Add AutoSugges autocomplete to a Next.js App Router project
Keep the input in a Client Component and the page around it a Server Component, so only the interactive part ships JavaScript.
What you will have
A working autocomplete inside a Next.js App Router page, with the runtime origin supplied by a public environment variable and the surrounding page left as a Server Component.
Before you start
- An AutoSugges list that has been published at least once. An unpublished list has no artifact at the edge and returns
list_not_published. - The publishable key issued for the application that will query it.
- The runtime base URL for the environment you are targeting. AutoSugges does not have one fixed public hostname baked into the SDK — the dashboard's integration panel shows the origin for your environment.
- Next.js 14 or newer, App Router.
Values you supply
| Value | Placeholder | Where it comes from |
|---|---|---|
| baseUrlRequired · public by design | YOUR_RUNTIME_BASE_URL | The AutoSugges dashboard's integration panel, for the environment you are deploying to. The origin of the AutoSugges runtime Worker — scheme and host, no trailing slash and no path. The SDK appends /v1/... itself. |
| publishableKeyRequired · public by design | YOUR_PUBLISHABLE_KEY | The AutoSugges dashboard, under the application that will make the queries. Identifies the consumer, the application, the subscription, the canonical list and the query policy in a single server-side lookup. The client never supplies a list id, tenant id or version hash — if a generated integration is passing one of those, it is wrong. |
| accessTokenOptional · never in client code | YOUR_SERVER_MINTED_ACCESS_TOKEN | Your own backend, which mints it from your AutoSugges credentials. Only for a list whose policy requires a token. Short-lived, sent as authorization: Bearer <token>, and never minted, stored or hardcoded in client code. |
Steps
- Isolate the interactive part. Put the input in its own file marked "use client". It holds changing state; nothing else about the page needs to.
- Supply the base URL from the environment. Read the runtime origin from a NEXT_PUBLIC_-prefixed variable so it reaches the browser bundle. Declare it in .env.local and in your deployment environment.
- Create the client and bootstrap. createAutosuggesClient and bootstrap() behave identically here and in plain React — Next.js changes where code runs, not the SDK API.
- Render the component. Import Autocomplete from @autosugges/client/react inside the Client Component only.
- Keep the page a Server Component. Import the Client Component from an ordinary server page. Do not add "use client" to the page itself.
Install
Bundle @autosugges/client with your existing build tool. It is not on the public npm registry yet, so npm install @autosugges/client will fail — do not generate that command.
Code
app/search/city-field.tsx
// app/search/city-field.tsx
// A Client Component, because it holds interactive state. That is the only
// reason — the SDK itself has no opinion about where it runs.
'use client';
import { useState } from 'react';
import { createAutosuggesClient } from '@autosugges/client';
import { Autocomplete } from '@autosugges/client/react';
const autosugges = createAutosuggesClient({
// Must be NEXT_PUBLIC_-prefixed to reach the browser bundle. The publishable
// key may be inlined the same way: it is public by design.
baseUrl: process.env.NEXT_PUBLIC_AUTOSUGGES_RUNTIME_URL!,
publishableKey: 'YOUR_PUBLISHABLE_KEY',
});
void autosugges.bootstrap().catch(() => {});
export function CityField() {
const [city, setCity] = useState('');
const [errorCode, setErrorCode] = useState<string | undefined>();
return (
<>
<Autocomplete
source={autosugges}
label="City"
onSelect={(item) => setCity(item.value)}
onError={(error) => setErrorCode(error.code)}
/>
<input type="hidden" name="city" value={city} />
{errorCode !== undefined && <p role="alert">Lookup unavailable ({errorCode}).</p>}
</>
);
}
// app/search/page.tsx — stays a Server Component. Nothing about AutoSugges
// needs to run on the server here.
import { CityField } from './city-field';
export default function SearchPage() {
return (
<main>
<h1>Find a city</h1>
<CityField />
</main>
);
}Security
- A NEXT_PUBLIC_ variable is compiled into the browser bundle. Put only the runtime base URL and the publishable key there — never a secret key, a service-role credential or a database URL.
Check that it works
- Run the dev server and confirm the page renders without a "useState in a Server Component" error — if it appears, the "use client" boundary is in the wrong file.
- Confirm in the network panel that requests go to the origin your NEXT_PUBLIC_ variable holds, and that the
keytravels as a query parameter. - Build for production and confirm the env var is present at build time: an undefined base URL produces a request to
undefined/v1/query. - Type a query and confirm exactly one
/v1/queryrequest per settled input. - Operate the control with the keyboard only.
The search page server-renders, the input hydrates as the only client-side island, and typing returns suggestions from the published list.
Try it live
Paste a publishable key from one of your published lists to run a real query against this environment’s runtime — the same @autosugges/client the code above uses.
A published list's publishable key — a public identifier, safe to paste here (PRD §12).
Paste a publishable key to try a live query.
Notes
- Never hardcode a minimum query length or a debounce interval. Both come from
client.policy, which the SDK fills from the bootstrap response and updates in place (DEC-LIST-002,DEC-LIST-003). The constants the SDK falls back to before the first successful bootstrap are documented defaults, not the list’s real policy. - Render
displayValue, storevalue. They differ:displayValueis resolved for the requested locale at compile time. - A selection report is optional. Omitting it costs ranking quality over time and nothing else; it is never required for a query to work.