# Routing

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Build Pages
Source: https://www.devtutorials.ai/learn/astro/build-pages/routing

> Pages and page file types, linking, static, index and nested routes, route priority, the underscore prefix, trailing slashes, dynamic routes with getStaticPaths() and props, rest parameters, Astro.params, redirects, rewrites and the custom 404 page.

## Routing

- **Route** = an address on your site, such as `/about` or `/blog/my-first-post`.
- **Routing** = deciding which page answers which address.
- Astro uses **file-based routing**: the path of a file in `src/pages` is the address.
- In this lesson, you will learn every kind of route, including pages made from data.

**Simple lesson:** in Astro, you make an address by making a file.

### A Real-World Example

- Asha now has five blog posts.
- She does not want to write five page files that look the same.
- She writes one file, `src/pages/blog/[slug].astro`, and a list of her posts.
- Astro builds one page per post: `/blog/why-astro`, `/blog/my-first-component` and so on.

Press **▶ Run**. Open a post from the list. Then open the page list in the address bar.

<p>This page is at /blog/{slug}.</p>
<a href="/blog">All posts</a>
`,
    "src/pages/blog/index.astro": `<h1>All posts</h1>
<ul>
    <li><a href="/blog/why-astro">Why I chose Astro</a></li>
    <li><a href="/blog/my-first-component">My first component</a></li>
    <li><a href="/blog/props-made-simple">Props made simple</a></li>
</ul>
`,
  }}
/>

What happened:

- `[slug]` in the file name is a placeholder.
- `getStaticPaths()` lists the values for it.
- Astro built three pages from one file, at build time.

### What You Will Learn

In this lesson, you will learn:

1. Which files become pages, and how to link them.
2. Static, index and nested routes.
3. How Astro picks a route when two could match.
4. How to hide a file from routing.
5. Dynamic routes with `getStaticPaths()`, params and props.
6. Rest parameters for any depth of path.
7. Redirects, rewrites and the 404 page.

## Pages

### What Pages Are

- **Page** = a file in `src/pages` that becomes a route.
- Each page builds to one HTML file (or many, for dynamic pages).

### Supported Page File Types

- `.astro` = an Astro component. The most common type.
- `.md` = a Markdown page. You will write these in **Markdown and MDX**.
- `.mdx` = Markdown with components. Needs the MDX integration.
- `.html` = a plain HTML page, used as it is.
- `.js` and `.ts` files in `src/pages` are **endpoints**, not pages. They send data. You meet them in **Endpoints and Server Data**.

### Linking Between Pages

- Use a normal `<a href="/about">` tag. Astro needs no special link component.
- Start the path with `/` so it works from every page.
- A browser loads each page as a new page. This is fast, because each page is plain HTML.

**Simple lesson:** a page is a file in `src/pages`. Link to it with a plain `<a>`.

## File-Based Routing

### Static Routes

- **Static route** = a fixed file name. `about.astro` is always `/about`.

### Index Routes

- **Index route** = a file named `index`. It is the main page of its folder.
- `src/pages/index.astro` is `/`.
- `src/pages/blog/index.astro` is `/blog`.

### Nested Routes

- **Nested route** = a file in a sub-folder. The folders become parts of the address.
- `src/pages/blog/2026/recap.astro` is `/blog/2026/recap`.

### Excluding Files with an Underscore

- A file or folder whose name starts with `_` is **not** a page.
- Use it for drafts or helper files that live in `src/pages`: `_draft.astro`, `_components/`.

<figure className="ast-fig ngc-fig">
<svg viewBox="0 0 640 230" role="img" aria-labelledby="fig-routes" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-routes">Animation: each file in src/pages is matched with its address. The file starting with an underscore gets no address.</title>
  <g style={{ fontFamily: "var(--mono)", fontSize: 13 }}>
    <text x="24" y="30" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontWeight: 700 }}>File in src/pages</text>
    <text x="420" y="30" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontWeight: 700 }}>Address</text>
    <text x="24" y="62" style={{ fill: "var(--ngc-purple)" }}>index.astro</text>
    <text x="24" y="96" style={{ fill: "var(--ngc-purple)" }}>about.astro</text>
    <text x="24" y="130" style={{ fill: "var(--ngc-purple)" }}>blog/index.astro</text>
    <text x="24" y="164" style={{ fill: "var(--ngc-purple)" }}>blog/2026/recap.astro</text>
    <text x="24" y="198" style={{ fill: "var(--ngc-slate)" }}>_draft.astro</text>
    <g style={{ fill: "var(--ngc-green)", fontWeight: 700 }}>
      <text x="420" y="62"><animate attributeName="opacity" values="0;1;1" keyTimes="0;0.15;1" dur="6s" repeatCount="indefinite" />/</text>
      <text x="420" y="96"><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.25;0.3;1" dur="6s" repeatCount="indefinite" />/about</text>
      <text x="420" y="130"><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.4;0.45;1" dur="6s" repeatCount="indefinite" />/blog</text>
      <text x="420" y="164"><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.55;0.6;1" dur="6s" repeatCount="indefinite" />/blog/2026/recap</text>
    </g>
    <text x="420" y="198" style={{ fill: "var(--ngc-red)", fontWeight: 700 }}><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.7;0.75;1" dur="6s" repeatCount="indefinite" />no page</text>
  </g>
  <g style={{ stroke: "var(--ngc-slate)", strokeWidth: 1.5, strokeDasharray: "4 4" }}>
    <line x1="220" y1="58" x2="405" y2="58" /><line x1="220" y1="92" x2="405" y2="92" /><line x1="220" y1="126" x2="405" y2="126" /><line x1="220" y1="160" x2="405" y2="160" /><line x1="220" y1="194" x2="405" y2="194" />
  </g>
</svg>
<figcaption>Folders become parts of the address. Files starting with _ are skipped.</figcaption>
</figure>

Press **▶ Run**. Use the address bar to list every page. `_draft.astro` is not there.

<a href="/blog">Blog</a>
`,
    "src/pages/blog/index.astro": `<h1>Blog</h1>
<a href="/blog/2026/recap">2026 recap</a>
`,
    "src/pages/blog/2026/recap.astro": `<h1>My 2026 recap</h1>
<p>File: src/pages/blog/2026/recap.astro</p>
<a href="/blog">Back</a>
`,
    "src/pages/_draft.astro": `<h1>Not ready yet</h1>
`,
  }}
/>

### Route Priority

- Sometimes two routes match one address. `blog/new.astro` and `blog/[slug].astro` both match `/blog/new`.
- Astro picks the **more specific** route:
  - a static route beats a dynamic one,
  - a named parameter `[slug]` beats a rest parameter `[...path]`,
  - a longer static path beats a shorter one.
- So `/blog/new` shows `new.astro`, even if `[slug]` also lists `new`.

### Trailing Slashes

- **Trailing slash** = a `/` at the end of an address, as in `/about/`.
- The `trailingSlash` option in `astro.config.mjs` decides what Astro accepts:
  - `"ignore"` (the default) = both `/about` and `/about/` work.
  - `"always"` = only `/about/` works.
  - `"never"` = only `/about` works.
- Pick one style and use it in every link.

Changing `trailingSlash` after your site is live changes many addresses. Old links can break, so add redirects when you change it.

**Simple lesson:** files map to addresses, `index` is a folder's main page, `_` hides a file, and specific routes win.

## Dynamic Routes

### Dynamic Parameters

- **Dynamic route** = a file with `[name]` in its path. One file makes many pages.
- **Parameter** = the `[name]` part. Its value comes from the address.
- `src/pages/blog/[slug].astro` matches `/blog/anything`.

### getStaticPaths()

- In a static site, Astro must know every page at build time.
- So a dynamic page must export a function called `getStaticPaths()`.
- It returns a list. Each item has `params`: the value for each `[name]`.
- One item means one page.

### Passing Props Through getStaticPaths()

- Each item can also have `props`, such as the post's title and text.
- The page reads them with `Astro.props`.
- This saves looking the data up again.

### Reading Astro.params

- `Astro.params` holds the values from the address.
- In `[slug].astro`, `Astro.params.slug` is the part after `/blog/`.

Press **▶ Run**. Then add a fourth project to the list and run again.

<p>★ {project.stars} stars</p>
<p><small>Address part: {id}</small></p>
<a href="/projects/todo-app">Todo</a> ·
<a href="/projects/weather">Weather</a> ·
<a href="/projects/quiz">Quiz</a>
`,
  }}
/>

- The preview opens `/projects/todo-app`. Click the links, or pick a page from the address bar.
- `map()` turns the data list into the list of paths.

### [TS] Typing getStaticPaths() Results

- Astro has a helper type so `Astro.params` and `Astro.props` know their shape:

```ts
import type { GetStaticPaths, InferGetStaticPropsType } from "astro";

export const getStaticPaths = (() => {
  return [{ params: { id: "todo-app" }, props: { name: "Todo app" } }];
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { name } = Astro.props as Props;
```

- **[JS]** You do not need this. `Astro.params` and `Astro.props` work the same way.

`getStaticPaths()` runs once, at build time, before any page is made. It cannot read the visitor's address or cookies. Pages that need those are built on request. You will learn that in **On-Demand and Mixed Rendering**.

**Simple lesson:** `[name]` in the file name, `getStaticPaths()` lists the values, `Astro.params` reads them.

## Rest Parameters

- **Rest parameter** = `[...name]`. It matches any number of address parts, including none.
- `src/pages/docs/[...path].astro` matches `/docs`, `/docs/install` and `/docs/guides/deploy/netlify`.
- Its value is the whole rest of the path as one string, such as `"guides/deploy"`.
- For `/docs` itself, return `path: undefined`.

<p>Parts: {parts.length}</p>
<a href="/docs">Docs home</a> ·
<a href="/docs/install">Install</a> ·
<a href="/docs/guides/deploy">Deploy guide</a>
`,
  }}
/>

**Simple lesson:** `[...path]` catches a path of any length. Split it if you need the parts.

## Redirects, Rewrites and the 404 Page

### Configuring redirects

- **Redirect** = send a visitor from an old address to a new one.
- List them in `astro.config.mjs` under `redirects`:

```js
export default defineConfig({
  redirects: {
    "/old-blog": "/blog",
    "/posts/[slug]": "/blog/[slug]",
  },
});
```

- The second line is a **dynamic redirect**: `[slug]` is copied from the old address to the new one.

### Redirect Status Codes

- **Status code** = a number the server sends with every answer.
- `301` = moved for good. Search engines move the old page's ranking to the new one. This is the default.
- `302` = moved for now.
- Pick a code: `"/old-blog": { status: 302, destination: "/blog" }`.

### Rewrites vs Redirects

- A **redirect** tells the browser "go to another address". The address bar changes.
- A **rewrite** shows another page's content at the **same** address. The address bar does not change.
- You rewrite with `return Astro.rewrite("/some-page")` in a page's script.
- Rewrites are mostly used with on-demand pages, such as showing a "please log in" page at a private address.

### The 404 Page

- **404** = the status code for "page not found".
- Make `src/pages/404.astro`. Astro shows it for every address that has no page.
- A good 404 page links back to the home page.

Press **▶ Run**. Click each link. One is redirected. One has no page.

<ul>
    <li><a href="/blog">Blog</a></li>
    <li><a href="/old-blog">Old blog link</a></li>
    <li><a href="/missing">A page that does not exist</a></li>
</ul>
`,
    "src/pages/blog.astro": `<h1>Blog</h1>
<a href="/">Home</a>
`,
    "src/pages/404.astro": `<h1>Page not found</h1>
<p>Sorry, this page does not exist.</p>
<a href="/">Go home</a>
`,
  }}
/>

- The terminal shows `301 redirect: /old-blog -> /blog`.
- On a real host, the redirect is sent as a 301 answer. In a static build, Astro writes a small HTML page that sends the browser on.

When you rename a page, add a redirect from the old address. Old links in search results and on other sites keep working.

**Simple lesson:** redirects move visitors, rewrites swap content, and `404.astro` catches every missing page.

## TL;DR

1. **Route** = an address. **File-based routing** = file path in `src/pages` becomes the route.
2. **Static** `about.astro` → `/about`. **Index** `blog/index.astro` → `/blog`. **Nested** `blog/2026/recap.astro` → `/blog/2026/recap`.
3. **`_` prefix** = not a page.
4. **Priority** = static beats dynamic. `[slug]` beats `[...path]`.
5. **Dynamic route** = `[slug].astro` plus `getStaticPaths()`:
   - `params` = the values in the address,
   - `props` = extra data for the page.
6. **Astro.params** = read the address values.
7. **Rest parameter** = `[...path]`, any depth. `undefined` for the folder itself.
8. **Redirect** = `redirects` in the config. `301` by default.
9. **Rewrite** = `Astro.rewrite()`, same address, other content.
10. **404 page** = `src/pages/404.astro`.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No `getStaticPaths()` in `[slug].astro` | Astro finds the values | Export `getStaticPaths()` | Build error: the function is missing |
| Different names in file and code | `params.id` works in `[slug].astro` | Use the name in the brackets: `slug` | The value is `undefined` |
| Expecting `/blog/new` from `[slug]` when `new.astro` exists | Dynamic routes win | Static routes win | You see the static page |
| `<a href="about">` | Relative links are fine | Start with `/`: `/about` | The link breaks on nested pages |
| Renaming a page with no redirect | Old links update themselves | Add a redirect | Old links show the 404 page |
| No `404.astro` | The host makes a nice one | Make your own | Visitors see a plain error page |

</div>

## Check Yourself

## Exercises

## What to Remember

- The path of a file in `src/pages` is its address.
- `index` files are folder main pages. `_` files are not pages.
- `[slug].astro` plus `getStaticPaths()` builds many pages from one file.
- `Astro.params` reads address values. `props` carry extra data.
- Use redirects when pages move, and always make a `404.astro`.

Next: **Styling**, where Asha's pages get their colours and layout.
