Next.js App Router
A complete server/client split for a same-origin Domain0 flow.
Keep connection creation in a Route Handler or Server Action. Keep the dialog launcher in a Client Component. The browser receives only connection-scoped access.
Environment
DOMAIN0_API_KEY=d0_replace_me
DOMAIN0_API_URL=https://domain0.dev/api/domain0/Validate these variables in your server environment module. Do not add
NEXT_PUBLIC_ to the key.
Server client module
import 'server-only'
import { createDomain0PlatformClient } from 'domain0'
import { z } from 'zod'
const env = z.object({
DOMAIN0_API_KEY: z.string().min(8),
DOMAIN0_API_URL: z.url({ protocol: /^https$/ }),
}).parse(process.env)
export const domain0Platform = createDomain0PlatformClient({
baseUrl: env.DOMAIN0_API_URL,
apiKey: env.DOMAIN0_API_KEY,
})Connection bridge
Use the SDK helper in the catch-all route shown in the quickstart. It has a fixed upstream origin and a route allowlist, caps request bodies, and strips cookies and API keys.
Token refresh
Return a refresh endpoint that authorizes the signed-in user against your own
connection record, then calls issueConnectionToken() again. Give the browser
client an async resolver:
let access = initialAccess
let refresh: Promise<typeof access> | undefined
const client = createDomain0Client({
baseUrl: '/api/domain0/',
token: async () => {
if (Date.parse(access.expiresAt) - Date.now() > 60_000) {
return access.accessToken
}
refresh ??= refreshConnectionAccess(connectionId).finally(() => {
refresh = undefined
})
access = await refresh
return access.accessToken
},
})This coalesces concurrent refreshes and avoids issuing a new token for every polling request.
Refresh your UI after success
await domain0.connectDomain({
client,
connectionId,
onSuccess: () => router.refresh(),
onConnectionChange: () => {
void queryClient.invalidateQueries({ queryKey: ['domains'] })
},
})Invalidate client queries that own live domain data, then use router.refresh()
for Server Component data. Do not reload the entire browser document.