File-Based Routing
The file system IS the router. No config files, no URL mapping — just directories and files.
On this page
The Rule
If you are new to Astro, start with the mental model. After this, the next step is .astro files, then components and layouts.
Every file in src/pages/ becomes a URL. The directory structure maps directly to the URL path.
Each file in src/pages becomes a URL. index.astro serves the root, about.astro the about page, and the dynamic [kind]/[...slug].astro renders one URL per entry.
graph LR
accTitle: File-based routing — pages to URLs
accDescr: Each file in src/pages becomes a URL. index.astro serves the root, about.astro the about page, and the dynamic [kind]/[...slug].astro renders one URL per entry.
idx["src/pages/index.astro"] --> u1["/"]
abt["src/pages/about.astro"] --> u2["/about/"]
dyn["src/pages/[kind]/[...slug].astro"] --> u3["/guide/astro-mental-model/ (dynamic)"]
src/pages/
├── index.astro → /
├── about.astro → /about/
└── [kind]/
└── [...slug].astro → /guide/astro-mental-model/ (dynamic)
That’s it. No routing config. No urls.py. No app.get('/blog/:id'). The filesystem is the router.
This is like how Apache/nginx serve files from a directory. If you put blog/index.html in the document root, visiting /blog/ serves that file. Astro does the same thing, but compiles .astro files to .html first.
Static Pages
A file like src/pages/about.astro generates exactly one HTML page at /about/. You write it, Astro compiles it, done.
---// This code runs at build timeconst title = "About krowdev";---
<html> <body> <h1>{title}</h1> <p>A developer knowledge base.</p> </body></html>Index Pages
index.astro in any directory becomes the default page for that path:
src/pages/index.astro→/(homepage)src/pages/[kind]/[...slug].astro→/guide/astro-mental-model/
Same as index.html in traditional web hosting, or __init__.py in a Python package — it’s what you get when you navigate to the directory itself.
Dynamic Routes: [...slug].astro
This is where it gets powerful. Square brackets mean “parameterized.” The ... means “catch all path segments.”
src/pages/[kind]/[...slug].astro can generate:
/guide/agentic-coding-getting-started//guide/agentic-coding-prompt-patterns//guide/astro-mental-model/
But Astro needs to know which pages to generate at build time. That’s what getStaticPaths() does:
---export async function getStaticPaths() { const entries = await getCollection('kb');
// Return one path per kb entry return entries.map(entry => ({ params: { kind: entry.data.kind, slug: entry.id }, // what goes in the URL props: { entry }, // data passed to the page }));}---Think of getStaticPaths() as a Makefile pattern rule. Instead of writing one rule per file:
dist/guide/getting-started.html: content/kb/getting-started.mddist/guide/prompt-patterns.html: content/kb/prompt-patterns.mdYou write one pattern:
dist/guide/%.html: content/kb/%.md $(COMPILE) $< -o $@getStaticPaths() is telling Astro: “here are all the % values — generate one page for each.”
What Happens During Build
When you run npm run build:
- Astro scans
src/pages/ - Static pages (
index.astro,about.astro) → compiled once each - Dynamic pages (
[...slug].astro) →getStaticPaths()is called → one HTML file per returned path - All output lands in
dist/
Build: src/pages/index.astro → dist/index.html src/pages/[kind]/[...slug].astro → dist/guide/agentic-coding-getting-started/index.html → dist/guide/astro-mental-model/index.html → dist/article/welcome-to-krowdev/index.html → ... (one per kb entry)Challenge: Trace the route
Look at krowdev’s actual page files:
find src/pages -name "*.astro" | sortFor each file, write down:
- What URL(s) does it generate?
- Is it static (one page) or dynamic (many pages)?
- If dynamic, what collection does it iterate over?
Answer
| File | URL(s) | Type |
|---|---|---|
pages/index.astro | / | Static (1 page) |
pages/[kind]/[...slug].astro | /guide/astro-mental-model/, /article/welcome-to-krowdev/, … | Dynamic (1 per kb entry) |
Sources
- Astro Docs, Routing
- Astro Docs, Pages
- Astro Docs, Dynamic routes