> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> Use Fimo in Next.js App Router and Pages Router projects.

Next.js projects use separate server and client Fimo surfaces. The application keeps its file-system routes, rendering policy, locale infrastructure, links, and per-page metadata overrides; Fimo supplies content, labels, editing context, and the site-wide SEO defaults from Studio's SEO settings.

New Fimo projects pin Next.js to 16.4.2, which includes the stable fix for Pages Router development hydration. Existing projects keep their installed Next.js version until you update it.

Install `fimo-next` and `fimo-react` alongside `fimo` at the same version:

> [tab] npm
>
> ```bash theme={null}
> npm install fimo fimo-react fimo-next
> ```

> [tab] Bun
>
> ```bash theme={null}
> bun add fimo fimo-react fimo-next
> ```

> [tab] pnpm
>
> ```bash theme={null}
> pnpm add fimo fimo-react fimo-next
> ```

> [tab] Yarn
>
> ```bash theme={null}
> yarn add fimo fimo-react fimo-next
> ```

## Configure the runtime

```json theme={null}
{
  "runtime": { "framework": "nextjs" }
}
```

Next.js owns its route output and 404 behavior, so it needs no catch-all rewrite.

## Connect the application

Render `FimoProvider` once at the application root. It resolves the connection to your Fimo site on the server, hands only the public connection (tenant content URL, Branch, optional preview script) to the browser, and supplies React Query, labels, and the Studio preview script to everything below it. You do not create, copy, or read Fimo runtime environment variables.

* Locally, the connection comes from the linked site (`.fimo/project.json`) and the active git branch, so `git checkout` switches Branches.
* Hosted previews and production builds receive it from the platform.
* To point the app at another host, set `FIMO_CONTENT_API_URL` (and optionally `FIMO_ENV`); an explicit value always wins over derivation.

`withFimo` in `next.config` adds the hosted preview origins to `next dev`. If you do not set Next.js's `devIndicators`, Fimo hides it in hosted Previews while local `next dev` keeps Next's default. Any explicit `devIndicators` setting is preserved:

```ts theme={null}
const nextConfig = {
  devIndicators: { position: 'bottom-left' },
};

export default withFimo(nextConfig);
```

The wrapper is not the application's Fimo connection.

## Generate content clients

Create `src/schemas/<Uid>.json` and run `fimo schemas push <Uid>`. Fimo generates:

* `<Uid>.ts` for server reads and serialization;
* `<Uid>.client.ts` for React Query hooks and hydration;
* `<Uid>.types.ts` as an internal shared type module;
* `index.ts` as the generated content registry.

Server Components and data functions use `fimo-next` plus generated server modules. Client Components use `fimo-next/client` plus generated `.client.ts` modules. Do not import client modules into server-only code.

## App Router

Wrap the root layout with `FimoProvider` from `fimo-next`. It is a Server Component, so no page, route, or component receives a runtime prop:

```tsx theme={null}
import { FimoProvider, getLabels } from 'fimo-next';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const labels = await getLabels({ locale: 'en' });

  return (
    <html lang="en">
      <body>
        <FimoProvider labels={labels.snapshot}>{children}</FimoProvider>
      </body>
    </html>
  );
}
```

Server Components can read and render tracked values directly:

```tsx theme={null}
import { getLabels, Text } from 'fimo-next';
import * as Article from '@/schemas/Article';

export default async function Page() {
  const [articles, labels] = await Promise.all([Article.list({ limit: 12 }), getLabels({ locale: 'en' })]);

  return <Text value={labels.t('articles.title')} as="h1" />;
}
```

Client Components use generated `.client` hooks and `useLabels` from `fimo-next/client` under that provider. `fimo-next` requires `@tanstack/react-query` as a peer dependency.

`FimoProvider` also owns the optional Studio preview runtime. Application code does not add a preview bridge, read Fimo preview environment variables, or inject a preview script.

### SEO values and routes

The App Router starter reads global values from [SEO settings](/docs/studio/seo.md) in
the root layout and converts them to Next's native exports:

```tsx theme={null}
import { getFimoSeo } from 'fimo/seo/server';
import { toNextMetadata, toNextViewport } from 'fimo-next/seo';

export const generateMetadata = () => toNextMetadata(getFimoSeo());
export const generateViewport = () => toNextViewport(getFimoSeo());
```

No pathname means global values only. Read an explicit pathname in a page to
apply its configured route SEO, including Home:

```tsx theme={null}
// app/page.tsx, with the same imports
export const generateMetadata = () => toNextMetadata(getFimoSeo({ pathname: '/' }));
```

Dynamic pages pass their real pathname and the content used by SEO templates:

```tsx theme={null}
// getArticle and articlePath are your application's existing helpers.
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const article = await getArticle((await params).slug);
  return toNextMetadata(
    getFimoSeo({
      pathname: articlePath(article),
      routeData: { article },
    })
  );
}
```

One dynamic route module covers all its slugs. Add a route's `generateViewport`
only when its theme color differs; otherwise it inherits the root color.
`{{article.title}}` uses the supplied content. Missing or empty template values
fall back for the entire field to the global value.

Set global values at `seo` and route overrides at `routes.<id>.seo` in your
existing Fimo config (normally `.fimo/config.json`), preserving other keys:

```json theme={null}
{
  "seo": { "title": "Journal", "description": "Notes from the field" },
  "routes": {
    "article": {
      "path": "/blog/:slug",
      "seo": { "title": "{{article.title}}", "description": "{{article.summary}}" }
    }
  }
}
```

`toNextMetadata` maps title, description, site name, site base URL, share image,
favicon, Twitter account, robots and social locale. `toNextViewport` maps theme
color, which Next renders separately from metadata. The converters are pure
and accept `FimoSEO` from `fimo/seo`. Missing SEO produces an empty object.

The Node reader uses Fimo's current or legacy config file, with optional
`rootDir` for monorepos. Invalid JSON and read failures other than a missing
file throw. Static output needs a rebuild after config changes; cached routes
follow your app's revalidation policy. Fetched content can stay cached across
builds, so use your data-cache invalidation policy and check the rendered value
after a content edit. For Edge or bundled config, use
`resolveSeoForPath(pathname, publicSeoConfig, routeData)` from `fimo/seo` and
pass its result to the same converters. Include the selected config in your
server deployment when using a custom config location.
An Edge page's ancestor layouts must also use Edge-safe data, not the Node reader.

> [!WARNING]
>
> Next replaces nested metadata objects such as `openGraph` and `alternates`; it does not merge them field by field.
> Existing apps should adopt only chosen Fimo fields and preserve section policies, generated social images, icons and
> existing SEO integrations. Do not spread every global default into a leaf.

Next derives social titles/descriptions and Twitter images from the final page
metadata when those fields are unset. Keep one title-suffix owner; use Next's
absolute title form when a Fimo template already includes the full suffix.
Next's layout title template does not apply to a page in that same segment;
give Home a complete title if it needs the same suffix.
Canonical URLs use the current route relative to the configured site base.
Custom canonical rules, hreflang, sitemaps and robots.txt remain yours.

When the SEO settings have a site name and URL, the starter's root layout also
renders the site's Organization and WebSite JSON-LD. `buildSiteSchemas` returns
nothing until both are set; `serializeJsonLd` escapes the JSON for a script tag:

```tsx theme={null}
import { buildSiteSchemas, serializeJsonLd } from 'fimo/seo';

// Inside <body> of src/app/layout.tsx
{
  buildSiteSchemas(getFimoSeo()).map((schema) => (
    <script
      key={String(schema['@type'])}
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}
    />
  ));
}
```

Page-specific JSON-LD, such as Article or Product, remains yours.

Existing `buildFimoMetadata(rootDir?)` and `buildFimoViewport(rootDir?)` calls from `fimo-next`
remain supported for global defaults. Their argument is a directory, not a route.

## Pages Router

Wrap the root in `pages/_app.tsx` with `FimoProvider` from `fimo-next/client`. Pages Router has no Server Component above `_app`, so this provider resolves the connection while the server renders `_app` and writes it into the document for the browser:

```tsx theme={null}
import { FimoProvider, type LabelsSnapshot } from 'fimo-next/client';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps<{ fimoLabels?: LabelsSnapshot }>) {
  return (
    <FimoProvider labels={pageProps.fimoLabels}>
      <Component {...pageProps} />
    </FimoProvider>
  );
}
```

`getStaticProps` and `getServerSideProps` must return plain JSON. Serialize tracked content before returning props, then hydrate it with the generated client module:

```tsx theme={null}
import * as Article from '@/schemas/Article';
import * as ArticleClient from '@/schemas/Article.client';
import { Text } from 'fimo-next/client';
import type { GetStaticProps, InferGetStaticPropsType } from 'next';

export const getStaticProps = (async () => {
  const result = await Article.list({ limit: 1 });
  const article = result.items[0];
  if (!article) return { notFound: true };

  return { props: { article: Article.serialize(article) }, revalidate: 60 };
}) satisfies GetStaticProps;

export default function Page({ article: data }: InferGetStaticPropsType<typeof getStaticProps>) {
  const article = ArticleClient.hydrate(data);
  return <Text value={article.title} as="h1" />;
}
```

Use the same pair with `getServerSideProps`. Static slug routes list slugs in `getStaticPaths` and load entries with `getBySlug()`.

> [card] [Labels and locales](/docs/cli/locales.md)
>
> Pass the locale already resolved by the Next.js application into Fimo reads and providers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.