Skip to content
Esc
navigateopen⌘Jpreview

Next.js

Generate the typed client with the CLI and prerender Stet content with the App Router.

Next.js does not build with Vite, so codegen runs through the CLI instead of the plugin. stet generate writes the same src/stet.gen.ts, and your build script runs it before next build.

You need an organization API key in STET_API_KEY; the quickstart covers creating one.

Install

npm install @stetcms/client @stetcms/cli server-only
pnpm add @stetcms/client @stetcms/cli server-only
yarn add @stetcms/client @stetcms/cli server-only
bun add @stetcms/client @stetcms/cli server-only

@stetcms/client is what the generated file imports at runtime. @stetcms/cli does the codegen and works as a dev dependency. server-only earns its place in step 3.

Configure and generate

npx stet init
npx stet generate

init writes a stet.config.ts; generate fetches your content model and writes src/stet.gen.ts. Commit the generated file: it contains no secrets, and with it the project type-checks without a key or a reachable Stet.

Read content in server components

Route every import of the generated client through one module that declares itself server-only:

import 'server-only';

export * from '@/stet.gen';
import { stet } from '@/lib/stet';

export default async function Blog() {
  const posts = await stet.posts.list();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Importing the client from a client component is now a build error instead of an API key in the browser bundle.

List collection entries with generateStaticParams to prerender every content page during next build:

import { stet } from '@/lib/stet';

export const dynamicParams = false;

export async function generateStaticParams() {
  const posts = await stet.posts.list();
  return posts.map((post) => ({ slug: post.slug }));
}

export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await stet.posts.get(slug);
  return <h1>{post.title}</h1>;
}

Wire it into the build

{
  "scripts": {
    "build": "stet generate && next build"
  }
}

Every build now starts from the current content model. For environments that build without a key, --if-key succeeds and keeps the committed client instead of failing. When the key is configured, content failures fail the build rather than publishing missing pages.

The Next.js example is this guide as a running app, wired to a seeded local Stet.

Was this page helpful?