The site uses Astro's Content Collections to manage documentation and resources. The configuration is defined in src/content.config.ts.
| Collection | Location | Description |
|---|---|---|
docs |
src/content/docs/ |
Versioned documentation pages (guides) |
api |
src/content/api/ |
Versioned API reference documentation |
pages |
src/content/pages/ |
Global pages: non-versioned docs, resources, support, and more |
blog |
src/content/blog/ |
Blog posts |
Note
For a guide on writing and publishing blog posts, see How to write a blog post.
Each content file requires frontmatter with the following properties:
---
title: 'Page Title' # Required: The page title
description: 'Description' # Optional: Page description for SEO
---Content files that need to use interactive components must use the .mdx extension instead of .md. MDX allows you to import and use Astro components directly inside your markdown content.
| Component | Import | Description |
|---|---|---|
Alert |
import Alert from '@components/primitives/Alert/Alert.astro' |
Contextual alerts with info, warning, and alert types |
---
title: My page title
description: A page with alerts
---
import Alert from '@components/primitives/Alert/Alert.astro';
Regular markdown content here.
<Alert type="info">This is an informational note.</Alert>
<Alert type="warning">This is a warning message.</Alert>
<Alert type="alert">This is a critical alert.</Alert>Component imports must be placed after the frontmatter block and before the content where they are used. Do not place imports inside the frontmatter (
---) block.
Show the same snippet as CommonJS / ESM / TypeScript variants behind a tab strip. Tag each fenced code block with a dialect language and the tabs are built for you — consecutive tagged blocks are grouped into a single CodeTabs, and its import is injected automatically (no manual import needed).
| Fence | Tab label | Highlighted as |
|---|---|---|
cjs |
CommonJS | JavaScript |
mjs |
ESM | JavaScript |
ts |
TypeScript | TypeScript |
```cjs
const express = require('express');
const app = express();
```
```mjs
import express from 'express';
const app = express();
```
```ts
import express, { Express } from 'express';
const app: Express = express();
```For labels other than these dialects, tag the blocks with tab="..." instead:
```js tab="npm"
npm install express
```
```js tab="pnpm"
pnpm add express
```The API reference lives in its own collection at src/content/api/, organized by version. Unlike docs, the API collection is not language-scoped — it is only available in English.
src/content/api/
├── 3x/
│ ├── api.mdx # API overview
│ └── api/
│ ├── application/ # express() and app methods
│ ├── middleware/ # Built-in middleware
│ ├── request/ # req object
│ └── response/ # res object
├── 4x/
│ ├── api.mdx
│ └── api/
│ ├── application/
│ ├── express/ # express module-level methods
│ ├── middleware/
│ ├── request/
│ ├── response/
│ └── router/ # Router (added in 4.x)
└── 5x/
├── api.mdx
└── api/
├── application/
├── middleware/
├── request/
├── response/
└── router/
- Versioned:
/en/5x/api/application→ Express 5.x application API - Non-versioned:
/en/api/application→ Defaults to Express 5.x
The Express.js documentation supports multiple versions. Content is organized by version in both the docs and api collections.
src/content/docs/
└── en/
├── 3x/ # Express 3.x documentation
├── 4x/ # Express 4.x documentation
└── 5x/ # Express 5.x documentation (default)
The version list, prefixes, and default version are defined once in src/config/versions.ts
(VERSIONS, VERSION_PREFIXES, DEFAULT_VERSION) and consumed everywhere else.
- Default Version:
5xis the current default version (isDefault: trueinVERSIONS) - Supported Versions:
5x,4x,3x - URL Patterns:
- Versioned:
/en/5x/api/→ Express 5.x API docs - Non-versioned:
/en/api/→ Defaults to Express 5.x
- Versioned:
-
Create your content file in the appropriate version directory:
src/content/docs/en/5x/guide/my-new-page.md -
The versioning system (configured in
src/pages/[lang]/[...slug].astro) will automatically:- Serve the page at
/en/5x/guide/my-new-page - Create non-versioned aliases for default version content
- Serve the page at
To add support for a new Express version (e.g., 6x):
-
Create content directories:
src/content/docs/en/6x/— Guides and documentationsrc/content/api/6x/— API reference
-
Update the version type in
src/config/types.ts:export type VersionPrefix = '6x' | '5x' | '4x' | '3x';
-
Add the version to
src/config/versions.ts— the single source of truth for the version list, prefixes, and default version:export const VERSIONS: VersionOption[] = [ { id: '6x', label: 'v6.x', isDefault: true }, { id: '5x', label: 'v5.x' }, { id: '4x', label: 'v4.x' }, { id: '3x', label: 'v3.x (deprecated)', isDeprecated: true }, ];
DEFAULT_VERSIONandVERSION_PREFIXESare derived from this array, and the routing (src/pages/[lang]/[...slug].astro), the sidebar, and the link-localization plugins all read from it — so you don't update the version in those places anymore. -
Update menu versioning in
src/config/menu/main.ts:- Add
'6x'to theversionedarrays
- Add
-
Review
omitFromentries insrc/config/menu/api.ts:- Determine which API features exist or are deprecated in the new version
Some documentation pages are shared across all versions and don't belong to a specific Express version (e.g., security updates, migration guides, performance best practices). These pages live in the pages collection instead of docs.
src/content/pages/
└── en/
├── advanced/
│ ├── best-practice-performance.md
│ ├── best-practice-security.mdx
│ ├── healthcheck-graceful-shutdown.md
│ └── security-updates.mdx
├── guide/
│ ├── database-integration.mdx
│ ├── migrating-4.mdx
│ └── migrating-5.mdx
├── resources/
│ ├── community.md
│ ├── contributing.md
│ ├── glossary.mdx
│ ├── utils.md
└── support.md
Pages with a slug starting with resources/ are treated as resource pages and won't show "Docs" in the breadcrumbs.
-
Create your content file in
src/content/pages/:src/content/pages/en/guide/my-global-page.md -
Add the menu item in
src/config/menu/docs.tswithglobal: true:{ href: `/guide/my-global-page`, label: 'menu.myGlobalPage', ariaLabel: 'menu.myGlobalPageAria', global: true, }
The
global: trueflag tells the sidebar to generate the link without a version prefix and prevents the version switcher from modifying its URL.
Write internal links without a language prefix (and usually without a version) — just the section path. Two plugins localize them at build time so every page links within its own language and version:
src/plugins/remark-rewrite-localized-links.mjs— Markdown links[text](/...).src/plugins/rehype-rewrite-localized-links.mjs— raw HTML/JSX anchors<a href="/...">(e.g. cards that wrap a<Card>component), which Markdown link syntax can't express.
Both share the logic — and the defaults — in src/plugins/rewrite-localized-links-core.mjs
(DEFAULT_PREFIXES = localizable sections, DEFAULT_VERSIONED_SECTIONS = sections that carry a
version; the latest version comes from src/config/versions.ts and the unversioned "global" pages
are derived from the menu). They're registered in astro.config.mjs with no options needed.
For a link authored in a Spanish, 5.x page:
| Write this | Renders as | Why |
|---|---|---|
/api/express |
/es/5x/api/express/ |
versioned section → file version injected |
/guide/routing |
/es/5x/guide/routing/ |
versioned docs page → file version injected |
/guide/migrating-5 |
/es/guide/migrating-5/ |
"global" page → no versioned URL, stays bare |
/4x/api/express |
/es/4x/api/express/ |
explicit version is preserved |
/resources/middleware |
/es/resources/middleware/ |
resources is never versioned |
Rules:
- Always omit the language. The plugin prepends the current page's language. Never write
/en/...— translations sync from Crowdin verbatim, so a hardcoded language would make translated pages link back to English. - Versioned sections (
api,starter,guide,advanced) carry a version: the link's own version is kept, or the source file's version (indocs/api) — or the latest (DEFAULT_VERSION) from a non-versioned collection — is injected when the link omits one. Write/api/expressor/guide/routing; in a 5.x file both resolve under/…/5x/…. - "Global" pages are the exception. A handful of
guide/advancedpages live in the unversionedpagescollection (migrating-5,best-practice-performance, …) and have no versioned URL, so links to them always stay unversioned. This list is derived from the menu items flaggedglobal: true(insrc/config/menu/) — no manual upkeep. (Adding a global page already requires that flag; see Adding a Global Page.) resources,support,blogare never versioned; links are language-only.- External URLs, relative links (
./x), pure anchors (#x) and already-localized links (/en/...) are left untouched.