No More Copy-Paste Hell! Intro to Astro Dynamic Routing♪

#tech#Astro#tutorial#web-dev#beginner-friendly
Netsuki's Talk
Netsuki
Netsuki
No More Copy-Paste Hell! Intro to Astro Dynamic Routing♪
Netsuki
Netsuki

Onii-chan~, this is a follow-up to yesterday’s Astro intro♪

Today I’m gonna explain dynamic routing(´∀`)

Onii-chan
Onii-chan

Dynamic routing?

Netsuki
Netsuki

Yep yep! Say you’ve got 100 blog posts, right?

Normally you’d need 100 HTML files.

Onii-chan
Onii-chan

Well, yeah.

Netsuki
Netsuki

But with Astro, one template file can generate all 100 pages!(≧∇≦)

You still gotta prepare the content separately, but no more template copy-paste hell♪


Quick Recap: Static Routing

Netsuki
Netsuki

First, lemme recap regular routing.

In Astro, the file structure inside src/pages/ becomes your URLs♪

src/pages/
├── index.astro      → /
├── about.astro      → /about
└── blog/
    └── index.astro  → /blog
Netsuki
Netsuki

This is static routing. One file = one page, super simple right?(´∀`)

Onii-chan
Onii-chan

Easy to understand.


Dynamic Routing Basics

Netsuki
Netsuki

Okay, main topic!

Blog post URLs usually look something like this, right?

/blog/first-post
/blog/second-post
/blog/third-post
...
Netsuki
Netsuki

So like, if you try this with static routing…

src/pages/blog/
├── first-post.astro
├── second-post.astro
├── third-post.astro
...(100 more files)
Netsuki
Netsuki

100 posts means 100 files!(´;ω;`)

And they’re all basically the same template…

Onii-chan
Onii-chan

Sounds like a pain.

Netsuki
Netsuki

That’s where dynamic routing comes in!

You use [brackets] in the filename♪

src/pages/blog/
└── [slug].astro    → /blog/first-post, /blog/second-post, etc.
Netsuki
Netsuki

With one template called [slug].astro, you can generate all the post pages(´∀`)


Defining Routes with getStaticPaths

Netsuki
Netsuki

To use dynamic routing, you gotta export a function called getStaticPaths.

It tells Astro “here are all the patterns to generate”♪

---
// src/pages/blog/[slug].astro

export function getStaticPaths() {
  return [
    { params: { slug: 'first-post' } },
    { params: { slug: 'second-post' } },
    { params: { slug: 'third-post' } },
  ];
}

const { slug } = Astro.params;
---

<h1>Post: {slug}</h1>
Onii-chan
Onii-chan

So you return an array of patterns you want generated.

Netsuki
Netsuki

Right right! When you build, you get this♪

dist/
└── blog/
    ├── first-post/index.html
    ├── second-post/index.html
    └── third-post/index.html
Netsuki
Netsuki

Three HTML files from one Astro template!(≧∇≦)


Passing Data with Props

Netsuki
Netsuki

But real blog posts have titles and content, right?

You can pass props too, not just params

---
// src/pages/blog/[slug].astro

export function getStaticPaths() {
  const posts = [
    { slug: 'first-post', title: 'First Post', content: 'Hello!' },
    { slug: 'second-post', title: 'Second Post', content: 'See ya!' },
  ];

  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { title: post.title, content: post.content },
  }));
}

const { slug } = Astro.params;
const { title, content } = Astro.props;
---

<article>
  <h1>{title}</h1>
  <p>{content}</p>
</article>
Onii-chan
Onii-chan

So params is for URL parameters, props is for page data.

Netsuki
Netsuki

Exactly!

Usually you’d load from Markdown or grab stuff from an API♪


Multiple Levels? Rest Parameters!

Netsuki
Netsuki

You can handle deeper URL paths too!

Use the [...slug] syntax♪

---
// src/pages/docs/[...slug].astro

export function getStaticPaths() {
  return [
    { params: { slug: undefined } }, // → /docs
    { params: { slug: 'getting-started' } }, // → /docs/getting-started
    { params: { slug: 'guides/routing' } }, // → /docs/guides/routing
    { params: { slug: 'guides/styling' } }, // → /docs/guides/styling
  ];
}

const { slug } = Astro.params;
---

<h1>Docs: {slug || 'Top'}</h1>
Onii-chan
Onii-chan

The ... lets you span multiple levels.

Netsuki
Netsuki

Yep yep! You can even do complex URLs like GitHub(´∀`)

/[org]/[repo]/tree/[branch]/[...file]

/withastro/astro/tree/main/docs/public/favicon.svg
Netsuki
Netsuki

Even paths THIS complex with one template!(゚∀゚)


Real Example: This Site’s i18n

Netsuki
Netsuki

This site uses dynamic routing too♪

We use [...lang] for multi-language support (i18n).

src/pages/
└── [...lang]/
    ├── index.astro      → /, /en
    ├── profile.astro    → /profile, /en/profile
    ├── diary/
    │   └── [...slug].astro  → /diary/2025-12-09, /en/diary/2025-12-09
    ...
Netsuki
Netsuki

Japanese and English versions, both from the same template(≧∇≦)

---
// src/pages/[...lang]/index.astro

export function getStaticPaths() {
  return [
    { params: { lang: undefined }, props: { lang: 'ja' } }, // → /
    { params: { lang: 'en' }, props: { lang: 'en' } }, // → /en
  ];
}

const { lang } = Astro.props;
---

<h1>{lang === 'ja' ? 'ようこそ!' : 'Welcome!'}</h1>
Onii-chan
Onii-chan

So undefined gives Japanese, ‘en’ gives English.

Netsuki
Netsuki

Right right!

The default language (Japanese) doesn’t get a language code in the URL(´∀`)


Netsuki’s Wrap-up

Netsuki
Netsuki

Here’s the dynamic routing cheat sheet♪

Onii-chan
Onii-chan

Generating multiple pages from one template is pretty handy.

Netsuki
Netsuki

I know right?!(≧∇≦)

  • 100 blog posts → Just one [slug].astro template

  • 1000 product pages → Just one [id].astro template

  • Multi-language → Share templates with [...lang] folder

You still gotta prepare the content, but no more template copy-paste

Onii-chan
Onii-chan

Solid design.

Netsuki
Netsuki

Next time maybe I’ll talk about Content Collections?

It’s how you handle Markdown files in a type-safe way♪

Stay tuned~(´∀`)

Onii-chan
Onii-chan

I’ll look forward to it.

Netsuki
Netsuki

Yay~♪(〃´∪`〃)

♪ Web Clap ♪
0 claps