From harness-claude
Guides Astro file-based routing via src/pages/ for static, dynamic ([slug]), and rest-parameter ([...slug]) routes. Covers getStaticPaths, pagination, redirects, 404s, and SSR changes.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> File-based routing maps your `src/pages/` directory structure to URLs — static, dynamic, and rest-parameter routes all follow the same filesystem convention.
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.
Builds REST API routes, webhooks, and form handlers in Astro projects using .ts endpoint files in src/pages/ and middleware for auth, cookies, logging. For SSR/hybrid mode with co-located backend.
Provides Astro framework patterns for islands architecture, content collections, rendering strategies (SSG, SSR, hybrid), view transitions, partial hydration, and Cloudflare deployment.
Share bugs, ideas, or general feedback.
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