Pre-rendering Dynamic React Routes with Meta Tags Using Vike + Vite

ara-ta3ara-ta3
3 min read

Introduction

Previously, I wrote an article titled How to Use Vike for SSG in a Vite, TypeScript, and React Project where I explored SSG with Vite and Vike.
In this article, I share my experience embedding meta information like title and description into dynamic pages (e.g., /hoge/:id) and performing SSG.

The versions of the tools used are as follows:

"@vitejs/plugin-react": "^4.4.1",
"typescript": "^5.8.3",
"vike": "^0.4.229",
"vike-react": "^0.6.1",
"vite": "^6.3.5"

GitHub Repository:
https://github.com/ara-ta3/vike-ssg-dynamic-pages-getting-started

Preparation

We'll be using the vike-react plugin.
In my previous article, I tried an approach without vike-react, but it seems rare not to use it, so this article assumes its use.

Vike settings are now defined in src/pages/+config.ts.
While they were previously in vike.config.js, doing so now triggers a warning:

23:00:00 [vike][Warning] Define Vike settings in +config.js instead of vite.config.js https://vike.dev/migration/settings

Here’s how the configuration file looks:
src/pages/+config.ts

import vikeReact from "vike-react/config";

export default {
  extends: [vikeReact],
  prerender: true,
};

Overview

Here’s what we’ll be doing:

  • Define dynamic route path in +route.ts

  • Create page content in +Page.tsx

  • Fetch title in +onBeforeRender.ts and pass to pageContext

  • Output meta info in +Head.tsx

  • List static paths in +onBeforePrerenderStart.ts

Final directory structure under src/pages/hoge:

src
└── pages
    ├── +config.ts
    ├── +Head.tsx
    ├── +Page.tsx
    └── hoge
        ├── +Head.tsx
        ├── +onBeforePrerenderStart.ts
        ├── +onBeforeRender.ts
        ├── +Page.tsx
        └── +route.ts

Defining the Dynamic Route (+route.ts)

Enable handling of dynamic route /hoge/@id:

export default "/hoge/@id";

https://vike.dev/route

Creating Page Content (+Page.tsx)

Props aren't passed directly, so use usePageContext().
The PageContext generic is defined as PageContext<Data = unknown>, so we use as to specify the data shape:

import { usePageContext } from "vike-react/usePageContext";
import { PageContext } from "vike/types";

export { Page };

function Page() {
  const c = usePageContext() as PageContext<{
    id: number;
    title: string;
    description: string;
  }>;
  const title = c.data.title;
  const description = c.data.description;

  return (
    <div>
      <p>hello {c.data.id}</p>
    </div>
  );
}

https://vike.dev/Page

Fetching Data and Passing Meta Info to PageContext (+onBeforeRender.ts)

Retrieve data and return it as pageContext:

import { PageContext } from "vike/types";

export async function onBeforeRender(pageContext: PageContext) {
  const id = parseInt(pageContext.routeParams.id);
  let title = "Hoge null";
  if (isNaN(id)) {
    return {
      pageContext: {
        data: { id, title, description: "Hoge null" },
      },
    };
  }
  title = `Hoge ${id}`;
  return {
    pageContext: {
      data: { id, title: `Hoge ${id}`, description: `Hoge ${id}` },
    },
  };
}

https://vike.dev/onBeforeRender

Outputting Meta Info (+Head.tsx)

Responsible for outputting meta info:

import React from "react";
import { usePageContext } from "vike-react/usePageContext";
import { PageContext } from "vike/types";

const Head: React.FC = () => {
  const c = usePageContext() as PageContext<{
    id: number;
    title: string;
    description: string;
  }>;

  const title = c.data.title;
  const description = c.data.description;

  return (
    <>
      <title>{title}</title>
      <meta name="description" content={description} />
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:type" content="website" />
      <meta charSet="UTF-8" />
    </>
  );
};

export default Head;

https://vike.dev/head-tags#head-setting

Listing Static Paths (+onBeforePrerenderStart.ts)

Enumerate the paths for prerendering:

export { onBeforePrerenderStart };

async function onBeforePrerenderStart() {
  const ids = ["1", "2"];
  return ids.map((id) => `/hoge/${id}`);
}

https://vike.dev/onBeforePrerenderStart

Build

Run vike build to generate the HTML files:

./node_modules/.bin/vike build
...
✓ 3 HTML documents pre-rendered.
✓ built in 633ms

Conclusion

Using Vite, Vike, and Vike-react, we were able to perform SSG for pages with dynamic IDs.
It was surprisingly straightforward, reaffirming how convenient this stack is.
However, Vike is still under active development, and small updates can cause warnings or break old setups.
We need to stay flexible and test things ourselves while adapting to changes.

I hope this article helps you get started with SSG for dynamic routes!

Reference

You can see this implementation live here:
https://nekometry.web.app/

This article is an English translation of the original Japanese article available at https://zenn.dev/ara_ta3/articles/typescript-vike-ssg-with-dynamic-routing.

0
Subscribe to my newsletter

Read articles from ara-ta3 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

ara-ta3
ara-ta3