domain
Guides

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

.env.local
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

lib/domain0.server.ts
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.

On this page