# Next.js SDK

> Install @rootsmithio/next in a Next.js App Router site so RootSmith pages render on your domain and refresh the moment you publish.

Source: https://docs.rootsmith.io/docs/publishing/nextjs.md

The SDK renders published RootSmith pages in your Next.js app, adds them to
your sitemap, and receives a signed refresh when you publish.

## Requirements

- Next.js 15 or 16 with the App Router, and React 19.
- A server runtime. For `output: "export"`, use
  [static sites](https://docs.rootsmith.io/docs/publishing/static-sites.md) instead.
- The site deployed at the primary domain saved for the site in RootSmith,
  over HTTPS.

## 1. Install the package

```npm
npm install @rootsmithio/next
```

## 2. Add environment variables

Open **Sites** → **Publishing** → **Set up SDK** in RootSmith to copy these
values. An agent can get the first two from the `get_sdk_setup` tool.

```bash title=".env.local"
ROOTSMITH_API_URL=https://api.example.com
ROOTSMITH_SITE_ID=00000000-0000-0000-0000-000000000000
ROOTSMITH_CONNECTION_SECRET=
```

| Variable                      | Value                                           |
| ----------------------------- | ----------------------------------------------- |
| `ROOTSMITH_API_URL`           | The RootSmith API address. Not your site's URL. |
| `ROOTSMITH_SITE_ID`           | The site ID from **Publishing**.                |
| `ROOTSMITH_CONNECTION_SECRET` | The connection secret from **Publishing**.      |

<Callout type="warn" title="Keep the secret on the server">
  Only a person can copy the connection secret from RootSmith. Add it to
  `.env.local` and to your host's server environment. Never commit it, never
  prefix it with `NEXT_PUBLIC_`, and never paste it into a chat with an agent.
</Callout>

Add the same three variables to your host, such as Vercel, for production.

## 3. Create the client

```ts title="lib/rootsmith.ts"
import { createRootSmith } from "@rootsmithio/next"

export const rootSmith = createRootSmith({
  apiUrl: process.env.ROOTSMITH_API_URL!,
  siteId: process.env.ROOTSMITH_SITE_ID!,
})
```

## 4. Render pages

Add a catch-all route that asks RootSmith for the requested path:

```tsx title="app/[...path]/page.tsx"
import "@rootsmithio/next/styles.css"
import { notFound } from "next/navigation"
import { pageMetadata } from "@rootsmithio/next"
import { RootSmithPage } from "@rootsmithio/next/markdown"
import { rootSmith } from "@/lib/rootsmith"

type Props = { params: Promise<{ path: string[] }> }

export async function generateStaticParams() {
  return []
}

async function get(params: Props["params"]) {
  const { path } = await params
  const page = await rootSmith.getPage(`/${path.join("/")}`)
  if (!page) notFound()
  return page
}

export async function generateMetadata({ params }: Props) {
  return pageMetadata(await get(params))
}

export default async function Page({ params }: Props) {
  return <RootSmithPage page={await get(params)} />
}
```

The empty `generateStaticParams` renders each page on its first visit, then
serves it from the Next.js cache.

Adapt the paths to the project:

- With a `src` folder, use `src/app/...` and `src/lib/...`.
- Without the `@/` import alias, use a relative import.
- If the app already has a root catch-all route, add one route for each page
  type's URL prefix instead, such as `app/blog/[...path]/page.tsx`. Pass the
  full path to `getPage`, such as `` `/blog/${path.join("/")}` ``.
- Wrap `RootSmithPage` in your site's layout or article shell as needed.

## 5. Add the refresh handler

```ts title="app/api/rootsmith/route.ts"
import { createRootSmithHandler } from "@rootsmithio/next"

export const POST = createRootSmithHandler()
```

RootSmith calls this route to verify the connection and to refresh a page
after you publish. It must be public. If your app uses auth middleware, a
`proxy.ts` or `middleware.ts` matcher, basic auth, or deployment protection,
let `POST /api/rootsmith` through. The handler checks its own signature.

## 6. List pages in your sitemap

```ts title="app/sitemap.ts"
import type { MetadataRoute } from "next"
import { rootSmith } from "@/lib/rootsmith"

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const pages = await rootSmith.listAllPages()
  return pages.map((page) => ({ url: page.url, lastModified: page.updatedAt }))
}
```

If the site already has a sitemap, add these entries to it.

## 7. Deploy and connect

1. Deploy the site with the three environment variables set.
2. In **Publishing**, enter `/api/rootsmith` as the **Delivery endpoint** and
   select **Save and verify**.
3. The status changes to **Connected**.

An agent does step 2 with `update_publishing_settings`, then
`verify_publishing_connection`. See [Install with an agent](https://docs.rootsmith.io/docs/agents/install.md).

## How refresh and caching work

- SDK reads use the Next.js data cache with the `rootsmith` tag for one
  hour. During `next build`, they last one second, so each build reads
  current content.
- When you publish, RootSmith sends a signed refresh. The handler expires the
  `rootsmith` tag, so the next request renders the new version.
- If a refresh is lost, the page corrects itself within an hour.
- If the refresh fails, RootSmith restores the previous version and tells
  you.
- When a page is merged into another, `getPage` sends a permanent (308)
  redirect to the page that replaced it.
- `getPage` returns `null` for an unknown path and for paths that can't be
  content, such as `/favicon.ico` or a path with a trailing slash.

## Customize

- **Styles**: `@rootsmithio/next/styles.css` matches the RootSmith preview.
  Override its `rootsmith-*` classes, or leave out the import and style the
  page yourself.
- **Elements**: pass React Markdown `components` to `RootSmithPage` to
  replace elements such as links, headings, or code blocks.
- **Images**: images load only from your site's origin by default. Pass
  `imageOrigins={["https://cdn.example.com"]}` to allow other hosts.
- **CDN caches**: pass a `refresh` function to purge your own cache. Call
  `revalidateTag(rootSmithTag, { expire: 0 })` in it too, and finish before
  it returns.

```ts title="app/api/rootsmith/route.ts"
import { revalidateTag } from "next/cache"
import { createRootSmithHandler, rootSmithTag } from "@rootsmithio/next"

export const POST = createRootSmithHandler({
  refresh: async (path) => {
    revalidateTag(rootSmithTag, { expire: 0 })
    await purgeMyCdn(path)
  },
})
```

Markdown supports headings, lists, links, URL images, quotes, fenced code,
tables, and task lists. Raw HTML is skipped, unsafe URL schemes are removed,
and no MDX or JavaScript runs.

## Reference

### `@rootsmithio/next`

| Export                                                   | Description                                                                                                                                   |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `createRootSmith({ apiUrl, siteId, fetcher? })`          | Returns the client below.                                                                                                                     |
| `client.getPage(path)`                                   | The published page at `path`, or `null`. Redirects merged pages with a 308.                                                                   |
| `client.listPages(page?)`                                | Up to 25 published pages sorted by path. Pages start at 1.                                                                                    |
| `client.listAllPages()`                                  | Every published page.                                                                                                                         |
| `pageMetadata(page)`                                     | Next.js `Metadata` with the title, description, canonical URL, and Open Graph article fields.                                                 |
| `createRootSmithHandler({ siteId?, secret?, refresh? })` | The `POST` route handler. Reads `ROOTSMITH_SITE_ID` and `ROOTSMITH_CONNECTION_SECRET` when a request arrives. Answers 503 until both are set. |
| `rootSmithTag`                                           | The cache tag, `"rootsmith"`.                                                                                                                 |
| `PublishedPage`                                          | The page type below.                                                                                                                          |

The client throws for other HTTP failures and invalid responses.

### `@rootsmithio/next/markdown`

| Export                                                                | Description                                                                                         |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `RootSmithPage({ page, components?, imageOrigins? })`                 | The title, description, and body in an `<article>` with the version marker.                         |
| `MarkdownContent({ markdown, baseUrl?, imageOrigins?, components? })` | The body only. Shows no images without `baseUrl` or `imageOrigins`. Doesn't add the version marker. |

### `PublishedPage`

```ts
interface PublishedPage {
  id: string
  siteId: string
  pageTypeId: string
  revision: number
  title: string
  path: string
  description: string
  markdown: string
  url: string
  publishedAt: string
  updatedAt: string
}
```
