Page routing

Page Router

What is the Page Router?

Val's page router allows you to create dynamic routes based on your content. By using s.router(nextAppRouter, schema), you can automatically generate pages for each entry in your content record.

Basic Setup

The documentation you're reading right now is built using Val's page router. Here's how it's structured:

// app/(main)/docs/[[...slug]]/page.val.ts
import { c, nextAppRouter, s } from "@/val.config";
import { docsSchema } from "./docsSchema.val";

export default c.define(
  "/app/(main)/docs/[[...slug]]/page.val.ts",
  s.router(nextAppRouter, docsSchema),
  {
    "/docs": {
      title: "Overview",
      sections: [/* ... */],
    },
    "/docs/init": {
      title: "Installation",
      sections: [/* ... */],
    },
    "/docs/getting-started": {
      title: "Getting started",
      sections: [/* ... */],
    },
    // ... more routes
  },
);

How It Works

The router maps keys in your record to URL paths. Each key becomes a route, and Next.js will generate the corresponding pages automatically.

// Use s.route() to create type-safe links
const navigationSchema = s.object({
  links: s.array(
    s.object({
      label: s.string(),
      href: s.route(), // Validated against all router modules
    })
  ),
});

export default c.define("/content/navigation.val.ts", navigationSchema, {
  links: [
    { label: "Overview", href: "/docs" },
    { label: "Installation", href: "/docs/init" },
    { label: "Getting Started", href: "/docs/getting-started" },
    // Val validates these are valid routes!
  ],
});

Rendering Routes in Server Components

In server components, use fetchValRoute to fetch route content based on URL parameters. This function is async and returns the content for the matching route, or null if no route matches.

// app/(main)/privacy/page.tsx - Server Component
import { fetchValRoute } from "@/val/val.rsc";
import pageVal from "./page.val";
import { notFound } from "next/navigation";

export default async function PrivacyPage({ params }: { params: unknown }) {
  const page = await fetchValRoute(pageVal, params);
  if (!page) {
    return notFound();
  }
  return (
    <div>
      <h1>{page.title}</h1>
      {/* Render your content */}
    </div>
  );
}

Server Component Setup

Make sure to set up fetchValRoute in your val/val.rsc.ts file:

// val/val.rsc.ts
import "server-only";
import { initValRsc } from "@valbuild/next/rsc";
import { config } from "../val.config";
import valModules from "../val.modules";
import { cookies, draftMode, headers } from "next/headers";

const { fetchValRouteStega: fetchValRoute } = initValRsc(
  config,
  valModules,
  { draftMode, headers, cookies }
);

export { fetchValRoute };

Rendering Routes in Client Components

In client components, use the useValRoute hook to access route content. This hook takes the Val module and the params Promise from Next.js and returns the content for the matching route.

// app/(main)/docs/[[...slug]]/page.tsx - Client Component
"use client";
import { useVal, useValRoute } from "@/val/val.client";
import pageVal from "./page.val";
import { notFound } from "next/navigation";

export default function DocsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const docsData = useValRoute(pageVal, params);

  if (!docsData) {
    return notFound();
  }
  return (
    <div>
      <h1>{docsData.title}</h1>
      {/* Render your content */}
    </div>
  );
}

Client Component Setup

Make sure to set up useValRoute in your val/val.client.ts file:

// val/val.client.ts
import "client-only";
import { initValClient } from "@valbuild/next/client";
import { config } from "../val.config";

const { useValRouteStega: useValRoute } = initValClient(config);

export { useValRoute };

Choosing Between Server and Client Components

Use server components (fetchValRoute) when:

  • The page content is mostly static

  • You want better SEO and initial page load performance

  • You don't need client-side interactivity for the content

Use client components (useValRoute) when:

  • You need client-side interactivity

  • You want real-time content updates in Val Studio

  • The page has other client-side state or effects