Most Rocket sites are made from ordinary static Pages. A hand-written home Page, an About Page, a
component reference Page, or a few standalone guides do not need a Page Collection. Create those as
normal Markdown or JavaScript Pages and let Rocket render one output document for each config.path.
Use a Page Collection when one Page needs to render a repeatable view of other Pages. Blog indexes, dated archives, release-note lists, author pages, and tag pages all follow this model: the content items stay as static Pages, while an archive Page queries the Page Registry and renders the matching entries.
Blog posts can stay as normal static Markdown Pages. Put the Page config in the Page's
js server block, and add Page Metadata that the archive can query:
export const config = {
path: '/posts/first-launch',
metadata: {
title: 'First Launch',
description: 'What changed in the first public launch.',
date: '2026-05-25',
tags: ['blog', 'launch'],
authors: ['Ada Lovelace'],
},
menu: false,
};
export const config = {
path: '/posts/component-notes',
metadata: {
title: 'Component Notes',
description: 'Patterns from the first component documentation pass.',
date: '2026-05-18',
tags: ['blog', 'components'],
authors: ['Grace Hopper'],
},
menu: false,
};
metadata.date must be a YYYY-MM-DD date string. Use metadata.tags for collection membership,
and use metadata.authors when the site needs author archives or byline filtering. menu: false
keeps posts out of the main navigation without hiding them from direct links, the Page Registry, or
the Sitemap.
Rocket exposes the Page Registry Query as pageData.pages. It returns Page Collection entries with
the Page path, URL, normalized metadata, source file, and original Page record:
const posts = pageData.pages.query({
tags: 'blog',
pathPrefix: '/posts/',
sortBy: 'date',
sortDirection: 'desc',
});
This query finds Pages under /posts/ that include the blog tag and sorts dated entries newest
first. Undated entries remain in the collection after dated entries. Use tags: ['blog', 'launch']
when every tag must be present, and add author or authors when an archive needs byline filters.
Create one static JavaScript Page to own the archive path:
import { html } from 'lit';
import { ssrRender } from '@rocket/js/ssr.js';
import { atlasDocLayout, atlasDocComponents as components } from '@rocket/js/layouts/atlasDoc.js';
import { siteData } from '../siteData.js';
export { components };
export const config = {
path: '/blog',
metadata: {
title: 'Blog',
description: 'Latest posts from the project.',
},
};
export const pagination = pageData => ({
pageSize: 10,
collection: pageData.pages.query({
tags: 'blog',
pathPrefix: '/posts/',
sortBy: 'date',
sortDirection: 'desc',
}),
});
export default async (_request, { pageData }) => {
const pagination = pageData.pagination;
pageData.content = html`
<h1>Blog</h1>
<ol>
${pagination.items.map(
post => html`
<li>
<a href=${post.url}>${post.metadata.title}</a>
<time datetime=${post.metadata.date ?? ''}>${post.metadata.date ?? 'Undated'}</time>
</li>
`,
)}
</ol>
<nav aria-label="Blog pages">
${pagination.previousPath ? html`<a href=${pagination.previousPath}>Previous</a>` : ''}
<span>Page ${pagination.currentPage} of ${pagination.totalPages}</span>
${pagination.nextPath ? html`<a href=${pagination.nextPath}>Next</a>` : ''}
</nav>
`;
return new Response(await ssrRender(atlasDocLayout(pageData, siteData)), {
headers: {
'Content-Type': 'text/html; charset=utf-8',
},
});
};
The pagination export turns the collection into generated archive Pages. pageData.pagination
contains the current slice of entries, the current page number, the total page count, and previous
or next archive paths for links.
The owning Page path is page 1. With config.path: '/blog', Rocket renders the first archive page
at /blog during runtime and dist/blog/index.html during a static build. Pagination links and
generated discoverability output use the canonical document path /blog/ for that first page.
Later archive pages are numbered child paths:
/blog/
/blog/2/
/blog/3/
Rocket does not generate /blog/1/. The numbered archive Pages are generated variants of the
owning JavaScript Page, so they do not become separate entries in pageData.pageRegistry or the
menu tree.
Generated archive Pages participate in Site Discoverability when the project enables those outputs
in rocket-config.js:
export default {
includeGlobs: ['src/pages/**/*.rocket.{md,js}'],
siteOrigin: 'https://docs.example.com',
siteDiscoverability: {
sitemap: true,
robots: true,
},
};
When the Sitemap is enabled, Rocket includes the archive page 1 path and every generated numbered
archive path unless the owning archive Page sets discoverability.sitemap: false.
When the Robots File is enabled, discoverability.robots: 'disallow' on the owning archive Page
emits Disallow directives for page 1 and every generated numbered archive path. The post Pages
keep their own discoverability settings; hiding or disallowing the archive does not automatically
hide or disallow the posts.
Run a build and inspect the generated archive paths:
npm run build
ls dist/blog dist/blog/2
Use PageData for the Page Registry Query and pagination reference, and Configuration for Sitemap and Robots File settings.