From harness-claude
File-based routing for Astro: static, dynamic, and rest-parameter routes map `src/pages/` to URLs. Covers `getStaticPaths()`, `paginate()`, redirects, and SSR differences.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:astro-routing-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> File-based routing maps your `src/pages/` directory structure to URLs — static, dynamic, and rest-parameter routes all follow the same filesystem convention.
File-based routing maps your
src/pages/directory structure to URLs — static, dynamic, and rest-parameter routes all follow the same filesystem convention.
/blog/[slug]) and must implement getStaticPaths()Create files in src/pages/ to define routes. The file path maps directly to the URL:
src/pages/index.astro → /src/pages/about.astro → /aboutsrc/pages/blog/index.astro → /blogsrc/pages/blog/first-post.astro → /blog/first-postUse bracket syntax for dynamic route segments. The bracket name becomes the param key:
src/pages/blog/[slug].astro → /blog/:slugsrc/pages/[lang]/about.astro → /:lang/aboutImplement getStaticPaths() in every dynamic route page used in SSG mode. It must return an array of { params } objects:
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post }, // pass data to avoid re-fetching in frontmatter
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
Use rest parameters ([...slug]) for nested dynamic routes and catch-all paths:
src/pages/docs/[...slug].astro matches /docs/, /docs/guide, /docs/guide/introAstro.params.slug — it will be undefined (root), 'guide', or 'guide/intro'Implement pagination with paginate() inside getStaticPaths():
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog');
const sorted = posts.sort((a, b) => b.data.pubDate - a.data.pubDate);
return paginate(sorted, { pageSize: 10 });
}
const { page } = Astro.props;
// page.data: current page items
// page.currentPage: page number (1-indexed)
// page.url.next / page.url.prev: adjacent page URLs
Create src/pages/404.astro for a custom 404 page. In SSR mode this file is served for unmatched routes; in SSG it depends on the host.
Use Astro.redirect() for programmatic redirects in SSR mode. In SSG, configure redirects in astro.config.mjs:
// astro.config.mjs
export default defineConfig({
redirects: {
'/old-path': '/new-path',
'/blog/[slug]': '/posts/[slug]', // dynamic redirect
},
});
/about beats [slug]/blog/[slug] beats /[...rest]Astro's file-based router has no runtime overhead in SSG mode — all route resolution happens at build time, producing a flat directory of .html files. In SSR mode, the router resolves requests at the edge/server using the same file structure.
getStaticPaths() contract:
This function is called once per dynamic route file during the build. It must return a serializable array — the return value is cached and cannot be async per-entry (do all async work inside the function before mapping). Every params combination returned becomes a generated page. If a param is missing from the return value, that URL will 404.
Passing props through getStaticPaths():
The props key in each getStaticPaths() return object is merged into Astro.props for that specific page. This is the correct way to pass fetched data (collection entries, API responses) into the page frontmatter without a second network/file-system round-trip.
params vs. Astro.params:
params in getStaticPaths() — the object you define that shapes the URL segmentsAstro.params — the resolved param values available in the frontmatter at render time. In SSG they match the params object you returned. In SSR they come from the live request URL.Endpoint routes:
Files in src/pages/ with .ts or .js extensions (not .astro) become API endpoints, not HTML pages. See astro-server-endpoints for that pattern.
Named groups and optional segments (Astro 4+):
[...slug] already acts as optional (can match zero segments)[slug] with a rest route fallback or handle undefined in SSRHybrid SSR and routing:
In output: 'hybrid' mode, every page is static by default. Add export const prerender = false to opt specific pages into SSR. Dynamic route pages in SSR do not need getStaticPaths() — Astro.params is populated from the live request.
https://docs.astro.build/en/guides/routing
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeComprehensive best practices, routing patterns, component architecture, and static site generation techniques for building high-performance Astro websites.
Builds content-focused websites with Astro's zero-JS islands architecture, multi-framework components (React/Vue/Svelte), and Markdown/MDX support. Triggers on .astro files, Astro.props, content collections.
Implements Next.js App Router advanced routing: dynamic [slug] and catch-all [...slug] routes, route groups (name), parallel @slot, intercepting modals (.)path, private _prefix folders, Route Handlers APIs, search params, programmatic navigation.