Search documentation

Search documentation

Pages

Routing

Connect Contoprix page slugs to frontend URLs.

The routing rule#

Your frontend route receives a browser path. Contoprix delivery needs that same public path.

Path mapping
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:

Recommended structure
app/
  page.tsx              -> CMS page at /
  [...slug]/
    page.tsx            -> every other CMS page

The 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.

src/contoprix/ContoprixRenderer.tsx
"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.

app/page.tsx
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.

app/[...slug]/page.tsx
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:

.env.local
CONTOPRIX_BASE_URL=https://api.example.com
CONTOPRIX_DELIVERY_KEY=your-delivery-key

The 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:

Nested page
await client.pages.getBySlug("/company/team", {
  languageCode: "en",
});

For navigation, use getAllChildPages. It returns lightweight summaries, including a resolved url.

Section navigation
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.

app/[...slug]/page.tsx
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#

ProblemCheck first
Page is 404Published content exists for the requested language and full path.
Home page is 404A Home page is published for the website and language.
Nested page failsThe request uses /parent/child, not only child.
Page is 403Page access is public and the delivery key targets the correct website.
Blocks are blankThe component registry contains each exact block code.
Content is staleRevalidate the route or wait for its cache lifetime after publishing.