The routing rule#
Your frontend route receives a browser path. Contoprix delivery needs that same public path.
Browser URL: https://example.com/about
Delivery path: /about
SDK call: client.pages.getBySlug("/about")
Browser URL: https://example.com/company/team
Delivery path: /company/team
SDK call: client.pages.getBySlug("/company/team")The SDK encodes each segment of a catch-all page path. Pass the normal path string; do not manually assemble delivery endpoint URLs.
Use a root route and a catch-all route#
In a Next.js App Router project, use two routes:
app/
page.tsx -> CMS page at /
[...slug]/
page.tsx -> every other CMS pageThe dedicated root route makes home-page behavior clear. The catch-all route maps any number of URL segments to a Contoprix page.
1. Create a page renderer#
Keep the registry and schema fallback in one client component.
"use client";
import { PageRenderer } from "@contoprix/react/client";
import type { ContoprixPage } from "@contoprix/types";
import components from "./components";
import { schemas } from "./schema";
export function ContoprixRenderer({ page }: { page: ContoprixPage }) {
return <PageRenderer page={page} components={components} schemas={schemas} />;
}PageRenderer handles block order plus optional header/footer and region-layout rendering. The registry maps Contoprix component codes to your own React components.
2. Fetch the home page#
client.pages.get() is the direct root-page delivery method.
import { notFound } from "next/navigation";
import { createContoprixClient } from "@contoprix/next/server";
import { ContoprixRenderer } from "@/contoprix/ContoprixRenderer";
export default async function HomePage() {
const client = createContoprixClient();
try {
const page = await client.pages.get({ languageCode: "en" });
return <ContoprixRenderer page={page} />;
} catch (error) {
const statusCode =
typeof error === "object" && error !== null && "statusCode" in error
? (error as { statusCode?: number }).statusCode
: undefined;
if (statusCode === 404) notFound();
throw error;
}
}You can also use getContoprixPage({ slug: "/" }). The dedicated get() method simply makes the root-page intent clear.
3. Fetch every other CMS page#
In Next.js 16, params is asynchronous. Await it before building the URL path.
import { notFound } from "next/navigation";
import { getContoprixPage } from "@contoprix/next/server";
import { ContoprixRenderer } from "@/contoprix/ContoprixRenderer";
type CmsPageProps = {
params: Promise<{ slug: string[] }>;
};
export default async function CmsPage({ params }: CmsPageProps) {
const { slug } = await params;
const path = "/" + slug.join("/");
try {
const page = await getContoprixPage({
slug: path,
languageCode: "en",
});
return <ContoprixRenderer page={page} />;
} catch (error) {
const statusCode =
typeof error === "object" && error !== null && "statusCode" in error
? (error as { statusCode?: number }).statusCode
: undefined;
if (statusCode === 404) notFound();
throw error;
}
}Delivery methods reject for a missing page rather than return null. Convert only the 404 case to notFound(); rethrow other errors so real credential and network problems remain visible.
4. Configure delivery on the server#
getContoprixPage and createContoprixClient read server-side environment variables:
CONTOPRIX_BASE_URL=https://api.example.com
CONTOPRIX_DELIVERY_KEY=your-delivery-keyThe delivery key resolves website context and needs delivery:read. Keep it server-side; do not use a NEXT_PUBLIC_ variable.
Pass languageCode explicitly when your site is localized. An explicit language needs a published page-content version in that exact language; delivery does not silently replace it with another locale.
Nested paths and navigation#
A child page might store the short slug team but have public route /company/team. Always fetch the full route path:
await client.pages.getBySlug("/company/team", {
languageCode: "en",
});For navigation, use getAllChildPages. It returns lightweight summaries, including a resolved url.
const children = await client.pages.getAllChildPages("/company", {
languageCode: "en",
recursive: false,
});
const links = children.map((page) => ({
label: page.name,
href: page.url,
}));Use recursive: false for direct children only. The default is true and includes descendants.
Optional static parameters#
For a manageable set of pages, build catch-all parameters from the sitemap.
import { generateContoprixStaticParams } from "@contoprix/next/server";
export async function generateStaticParams() {
return generateContoprixStaticParams({ languageCode: "en" });
}Keep dynamic params enabled if editors can publish new pages that were not known at build time.
Troubleshooting#
| Problem | Check first |
|---|---|
| Page is 404 | Published content exists for the requested language and full path. |
| Home page is 404 | A Home page is published for the website and language. |
| Nested page fails | The request uses /parent/child, not only child. |
| Page is 403 | Page access is public and the delivery key targets the correct website. |
| Blocks are blank | The component registry contains each exact block code. |
| Content is stale | Revalidate the route or wait for its cache lifetime after publishing. |