Dynamic Page Rendering
Relevant source files
The following files were used as context for generating this wiki page:The railways-docs project employs a hybrid rendering strategy, combining Static Site Generation (SSG) with a dynamic, on-demand Server-Side Rendering (SSR) system. The dynamic rendering is primarily handled by a combination of Next.js Middleware and a dedicated dynamic route. This system is responsible for intercepting requests for documentation pages, determining the desired format (HTML or raw Markdown), and rendering the content accordingly.
This dynamic approach provides the flexibility to serve content in different formats without a full server-side architecture for every page. It is triggered for specific documentation paths and can deliver either a fully rendered HTML page or the raw Markdown source, based on the request headers or query parameters. This is distinct from the primary SSG approach which pre-renders most pages at build time for performance.
Content Source and Schema
All documentation content originates from Markdown (.md) files located in the src/docs directory. The contentlayer library is configured to process these files, transforming them into structured data that can be queried within the application.
Sources: contentlayer.config.ts
Contentlayer Configuration
The contentlayer.config.ts file defines a Page document type. This configuration specifies the file path pattern for content, the fields to extract from each file's frontmatter, and computed fields like the page's URL.
// contentlayer.config.ts:5-27
const Page = defineDocumentType(() => ({
name: "Page",
filePathPattern: `**/*.md`,
contentType: "mdx",
fields: {
title: {
type: "string",
description: "The title of the page",
required: true,
},
description: {
type: "string",
description: "The description of the page",
required: true,
},
},
computedFields: {
url: {
type: "string",
resolve: doc => `/${doc._raw.flattenedPath}`,
},
},
}));
Sources: contentlayer.config.ts:5-27
This process generates a data source, allPages, which is an array of all processed Page objects. This array is the single source of truth for page content and metadata used by both static and dynamic rendering paths.
Page Data Structure
The Page document type defines the following fields for each documentation page.
| Field | Type | Required | Description | Source |
|---|---|---|---|---|
title | string | Yes | The title of the page from frontmatter. | contentlayer.config.ts:11-15 |
description | string | Yes | The description of the page from frontmatter. | contentlayer.config.ts:16-20 |
url | string | Yes | The computed URL path for the page. | contentlayer.config.ts:22-26 |
body.code | string | Yes | The compiled MDX content as a string. | src/pages/dynamic/[...slug].tsx:30 |
body.raw | string | Yes | The raw, unprocessed Markdown content. | src/pages/dynamic/[...slug].tsx:73 |
Request Handling via Middleware
The entry point for the dynamic rendering flow is the Next.js middleware defined in src/middleware.ts. This middleware intercepts all incoming requests and decides whether to rewrite them to the dynamic rendering engine.
Sources: src/middleware.ts
The middleware's logic is as follows:
- It first checks if the request is for an API route (
/api/...) and ignores it if so. - It then checks if the requested path corresponds to a valid documentation page by looking it up in a
Setof all known page URLs (pageUrlSet). - If the path is a valid doc page, it determines if the client is requesting raw Markdown. This is true if the path ends in
.md, theformat=mdquery parameter is present, or theAcceptheader preferstext/markdownovertext/html. - If any of these conditions are met, the request URL is rewritten to
/dynamic/..., which points to the dynamic rendering page. Theformat=mdquery parameter is explicitly set if Markdown was requested.
The following diagram illustrates the request flow through the middleware.
Sources: src/middleware.ts:15-34
Server-Side Dynamic Rendering
When a request is rewritten by the middleware to /dynamic/[...slug], it is handled by the page component in src/pages/dynamic/[...slug].tsx. This component uses getServerSideProps to fetch data and render the page on the server for each request.
Sources: src/pages/dynamic/[...slug].tsx
Data Fetching (getServerSideProps)
The getServerSideProps function is responsible for finding the correct page data and handling the two rendering modes: HTML and raw Markdown.
The following sequence diagram shows the process within getServerSideProps.
Component Rendering
The PostPage component receives the page object and other props from getServerSideProps.
- It uses the
useMDXComponenthook fromnext-contentlayer/hooksto get a renderable React component from thepage.body.codestring. - It defines a set of custom components (e.g.,
Collapse,Image,CodeBlock) that are passed to the MDX renderer, allowing Markdown content to use rich, interactive React components. - The final rendered content is wrapped in the
DocsLayoutcomponent, which provides the overall page structure, SEO, and navigation elements.
Sources: src/pages/dynamic/[...slug].tsx:27-48, src/layouts/DocsLayout.tsx
Comparison with Static Site Generation (SSG)
The project also uses a standard SSG approach for most documentation pages, defined in src/pages/[...slug].tsx. This provides a useful contrast to the dynamic system.
| Feature | Dynamic Rendering (dynamic/[...slug].tsx) | Static Generation ([...slug].tsx) |
|---|---|---|
| Data Fetching | getServerSideProps (runs on every request) | getStaticProps (runs at build time) |
| Path Generation | Handled by middleware rewrite | getStaticPaths (generates all paths at build time) |
| Output | HTML or raw Markdown | HTML only |
| Use Case | Serving content on-demand, enabling format negotiation (e.g., for curl or bots). | Pre-rendering pages for maximum performance and SEO. |
| Performance | Slower (server-rendered per request) | Faster (served from CDN) |
Sources: src/pages/dynamic/[...slug].tsx:50-76, src/pages/[...slug].tsx:53-76
The SSG getStaticPaths function iterates through allPages to tell Next.js which paths to pre-render.
// src/pages/[...slug].tsx:70-76
export const getStaticPaths: GetStaticPaths = async () => {
const paths = allPages.map(page => page.url);
return {
paths,
fallback: false,
};
};
Sources: src/pages/[...slug].tsx:70-76
Summary
The dynamic page rendering system in railways-docs is a powerful extension of the core static site. By leveraging Next.js middleware, it intercepts requests for documentation pages and routes them to a server-side rendering engine. This architecture enables flexible content delivery, allowing the server to respond with either a fully-rendered HTML page or raw Markdown content based on client needs. This dual-format capability is achieved without compromising the performance benefits of a primarily static site, providing a robust and versatile documentation platform.