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.
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 instead. - The site deployed at the primary domain saved for the site in RootSmith, over HTTPS.
1. Install the package
npm install @rootsmithio/next2. 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.
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. |
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.
Add the same three variables to your host, such as Vercel, for production.
3. Create the client
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:
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
srcfolder, usesrc/app/...andsrc/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 togetPage, such as`/blog/${path.join("/")}`. - Wrap
RootSmithPagein your site's layout or article shell as needed.
5. Add the refresh handler
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
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
- Deploy the site with the three environment variables set.
- In Publishing, enter
/api/rootsmithas the Delivery endpoint and select Save and verify. - The status changes to Connected.
An agent does step 2 with update_publishing_settings, then
verify_publishing_connection. See Install with an agent.
How refresh and caching work
- SDK reads use the Next.js data cache with the
rootsmithtag for one hour. Duringnext build, they last one second, so each build reads current content. - When you publish, RootSmith sends a signed refresh. The handler expires the
rootsmithtag, 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,
getPagesends a permanent (308) redirect to the page that replaced it. getPagereturnsnullfor an unknown path and for paths that can't be content, such as/favicon.icoor a path with a trailing slash.
Customize
- Styles:
@rootsmithio/next/styles.cssmatches the RootSmith preview. Override itsrootsmith-*classes, or leave out the import and style the page yourself. - Elements: pass React Markdown
componentstoRootSmithPageto 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
refreshfunction to purge your own cache. CallrevalidateTag(rootSmithTag, { expire: 0 })in it too, and finish before it returns.
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
interface PublishedPage {
id: string
siteId: string
pageTypeId: string
revision: number
title: string
path: string
description: string
markdown: string
url: string
publishedAt: string
updatedAt: string
}