@seedprotocol/next
The Next.js config helper for apps that use @seedprotocol/sdk in the browser: browser builds of the SDK in every graph and cross-origin isolation headers, under Turbopack or webpack.
@seedprotocol/next exports withSeed, which wraps your Next.js config so @seedprotocol/sdk loads in the browser under Turbopack or webpack. It points the SDK at its browser build, including when Next.js renders client components on the server, and sends the cross-origin isolation headers the database worker needs. It runs in Node when Next.js loads next.config, and adds no runtime API.
import { withSeed } from '@seedprotocol/next'In a Next.js app, use the SDK only in the browser: in client components, started by SeedClientGate or by client.init() in an effect. Using the SDK on the server, in route handlers or in server components that read items, isn't supported.
Install
npm install @seedprotocol/sdk @seedprotocol/react
npm install --save-dev @seedprotocol/nextnext 15.5 or later is a peer dependency. Keep every @seedprotocol/* package on the same version. Under pnpm the default isolated layout works, so the app needs no .npmrc setting.
You don't need to install ZenFS (@zenfs/core, @zenfs/dom): the SDK depends on it and mounts the browser file system itself during client.init. The exception is the fsModules option below.
withSeed
Returns your Next.js config with the settings the SDK needs added. It takes a config object or a config function, and returns the same kind.
function withSeed(nextConfig?: NextConfig, options?: SeedNextOptions): NextConfig
function withSeed(nextConfig: NextConfigFunction, options?: SeedNextOptions): NextConfigFunctionNextConfigFunction is Next.js's (phase, { defaultConfig }) => NextConfig | Promise<NextConfig>. It isn't exported.
import type { NextConfig } from 'next'
import { withSeed } from '@seedprotocol/next'
const nextConfig: NextConfig = {}
export default withSeed(nextConfig)Options
All options are optional.
| Name | Type | Default | Description |
|---|---|---|---|
isolationHeaders | boolean | { coop?: string; coep?: string; corp?: string } | true | true sends Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: credentialless and Cross-Origin-Resource-Policy: same-origin. Pass an object to change individual values (the rest keep their defaults), or false to send none |
headersSource | string | '/:path*' | The paths the headers apply to, in the source syntax of Next.js headers(). The default covers every path |
fsModules | string[] | [] | Module names aliased to ZenFS in browser builds, for your own code or another dependency that imports fs in the browser. The SDK doesn't. A name containing promises maps to @zenfs/core/promises, any other name to @zenfs/core. Server builds keep Node's fs |
dir | string | process.cwd() | The app's directory. withSeed resolves the SDK and next from it, and writes Turbopack alias paths relative to it. Pass the config file's directory (__dirname, or import.meta.dirname in next.config.mjs) when Next.js runs from another directory, such as a monorepo root. When the SDK can't be resolved from dir, withSeed adds no browser-entry aliases |
The alias resolves @zenfs/core from your app, so with fsModules set, add @zenfs/core to your dependencies at the exact version the SDK lists (see node_modules/@seedprotocol/sdk/package.json). Without it, a pnpm build fails with Module not found: Can't resolve 'fs'. Two copies of ZenFS at different versions break the file system.
import { withSeed } from '@seedprotocol/next'
export default withSeed(
{},
{
isolationHeaders: { coep: 'require-corp' },
fsModules: ['fs', 'fs/promises', 'node:fs', 'node:fs/promises'],
},
)The options type is exported too:
import type { SeedNextOptions } from '@seedprotocol/next'What it configures
Next.js bundles client components twice: once for the browser and once for server rendering. In the server graphs, Node conditions would pick the SDK's Node platform (libsql and its native bindings) and @seedprotocol/query's Node entry. withSeed aliases both to their browser builds in every graph, through turbopack.resolveAlias and through a webpack function. It finds them by resolving @seedprotocol/sdk from dir (by default the directory you run next in), so the SDK has to be installed in the app.
It also adds a headers() rule on headersSource with the isolation headers. Next.js applies it under next dev and next start.
Your own settings are kept and win where they overlap: your turbopack.resolveAlias entries replace the helper's for the same name, your webpack function runs first and its aliases stay, and a cross-origin isolation header your headers() sets is the one sent.
App Router setup
@seedprotocol/react is marked 'use client', so a server component such as app/layout.tsx can render SeedProvider:
import type { ReactNode } from 'react'
import { SeedProvider } from '@seedprotocol/react'
import { SeedGate } from './seed-gate'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<SeedProvider>
<SeedGate>{children}</SeedGate>
</SeedProvider>
</body>
</html>
)
}Put SeedClientGate in a client component. A server component can't pass a function such as onInitError to it:
'use client'
import type { ReactNode } from 'react'
import type { SeedConfig } from '@seedprotocol/sdk'
import { SeedClientGate } from '@seedprotocol/react'
import schema from '../seed.schema.json'
const config: SeedConfig = {
filesDir: '.seed',
endpoints: { filePaths: '/files', files: '.seed' },
schema,
}
export function SeedGate({ children }: { children: ReactNode }) {
return (
<SeedClientGate
initConfig={{ config }}
onInitError={(error) => console.error('Seed failed to start', error)}
>
{children}
</SeedClientGate>
)
}Components that call Seed hooks are client components too:
'use client'
import { useCreateItem, useItems } from '@seedprotocol/react'
export default function Home() {
const { createItem } = useCreateItem()
const { items, isLoading } = useItems({ modelName: 'Post' })
return (
<main>
<button onClick={() => createItem('Post', { title: 'Hello from Next.js' })}>New post</button>
<p>{isLoading ? 'Loading posts' : `${items.length} posts`}</p>
</main>
)
}seed.schema.json is the schema file from Add Seed to your app, and SeedClientGate covers its props and init errors.
Next.js 15
Next.js 15.5 works with webpack. Its Turbopack can't load SQLite WASM in the browser: it starts workers from blob: URLs and hands SQLite a WASM URL that can't be resolved there. create-next-app 15.5 adds --turbopack to both the dev and build scripts, so remove it from both:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}When withSeed runs under Next.js 15 with Turbopack, it logs:
[@seedprotocol/next] Next.js 15 with Turbopack cannot load SQLite WASM in the browser, so the Seed SDK will fail to initialize. Run `next dev` and `next build` without --turbopack (webpack), or upgrade to Next.js 16.The app still builds. In the browser the gate stays on its loading screen, the console shows Failed to parse URL from /_next/static/media/sqlite3.<hash>.wasm, and onInitError receives Error: Driver not initialized.
Next.js 16 uses Turbopack by default, and the SDK runs under it.
Production hosting
next start sends the headers from withSeed. A static export (output: 'export') has no Next.js server, so your host has to send the headers on the HTML pages; Headers in production has the values and host examples. To check a deployment, confirm self.crossOriginIsolated is true in the browser console.
Related
- Add Seed to your app: the Vite + React setup, with the schema file and client config
- @seedprotocol/react:
SeedProvider,SeedClientGateand the hooks - @seedprotocol/vite: the same setup for Vite apps
- The Seed toolkit: where each package fits
@seedprotocol/vite
The Vite plugin for browser apps that use @seedprotocol/sdk: cross-origin isolation headers, ES module workers, in-order dev pre-bundles, and opt-in Node polyfills and fs aliases.
Overview and initPublish
What @seedprotocol/publish does, how to install it, its two entry points, every initPublish option, item.publish(), the PublishManager API that starts, retries and stops publishes, and publish wallets.