Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 15

Dynamic Pages with getStaticPaths

Recommended resources

Hello! Welcome to the second lesson in our module on Dynamic Routing and Layouts.

In our previous lesson, you built a BaseLayout.astro component. This was a crucial step towards creating a maintainable website, as it allows you to define a consistent shell for all your pages. We now have a single place to manage the header, footer, and overall page structure.

However, so far we've only applied this layout to pages we create manually, like index.astro and about.astro. This works for a small, fixed set of pages. But what about your blog posts, services, or case studies? Your goal is to build a content-driven site where you can add new content without having to create a new .astro file each time. Manually creating src/pages/services/service-one.astro, src/pages/services/service-two.astro, and so on would be inefficient and unscalable.

Today, we will solve this by learning how to generate dynamic pages from your content collections using getStaticPaths. This is a powerful feature in Astro that lets you programmatically create pages at build time from your data.

The Core Concept: From Data to Pages

The process of generating pages dynamically in a static site generator like Astro involves two key parts:

  1. A Dynamic Route Template: Instead of a file with a fixed name like about.astro, you create a file with a placeholder in its name, such as [slug].astro. This file acts as a template for all pages of a certain type (e.g., all blog posts). The [slug] part is a parameter that will be filled in from your data.
  2. A Data-to-Path Mapper: You need a way to tell Astro, "For every item in my 'blog' collection, create a page using the template and use the post's slug for the URL." This is exactly what the getStaticPaths function does.

getStaticPaths is a special, required export for dynamic routes in Astro's default static mode. It runs at build time, fetches your data, and returns an array of all the pages that need to be generated.


How getStaticPaths Works

To understand the mechanics, the official Astro documentation provides the most direct and authoritative explanation.

Content collections - Astro Docs

This section of the Astro documentation, 'Generating Routes from Content,' explains exactly how to use getStaticPaths to create pages from your collections. It's the foundational knowledge for this lesson.

Please read the section titled 'Generating Routes from Content,' focusing specifically on the subsection 'Building for static output (default)'. Pay close attention to the structure of the object returned by getStaticPaths—it contains params and props.

As you've just read, getStaticPaths must return an array of objects. Each object in this array corresponds to one page that will be generated and must have two properties:

  • params: An object whose keys match the dynamic parameters in your file name. For a route like [slug].astro, you must provide a slug property (e.g., { slug: 'my-first-post' }). This determines the final URL of the page.
  • props: An object containing any data you want to pass to the page component. Typically, you'll pass the entire content entry (e.g., the post object) so you can access its title, date, and content on the page.

For a more visual and narrated walkthrough, the following video demonstrates this process from start to finish.

Astro 3.0 Crash Course (View Transitions, Image, Content Collections, and More!)

This video by James Q Quick provides a clear, practical demonstration of creating dynamic pages for a blog. It ties together content collections, dynamic routes, and the getStaticPaths function.

Watch from 00:30:54 to 00:37:32. The first part (until 00:33:20) covers the implementation of getStaticPaths. The second part shows how to use the passed props to render the post's title, image, and MDX content on the dynamically generated page.


A Practical Example: Building a Service Page Template

Let's bring this all together. Assume you have a content collection for your accounting services located at src/content/services/.

First, you would create the dynamic route file: src/pages/services/[slug].astro.

Inside this file, you'll implement the logic:

{/* src/pages/services/[slug].astro */}
---
import { getCollection, type CollectionEntry } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro'; // Import your layout

// 1. getStaticPaths generates the routes
export async function getStaticPaths() {
  const services = await getCollection('services');
  
  return services.map(service => ({
    params: { slug: service.slug }, // `service.slug` is the URL-friendly filename
    props: { service }, // Pass the full service object as a prop
  }));
}

// Define the type for the props we're receiving from getStaticPaths
interface Props {
  service: CollectionEntry<'services'>;
}

// 2. The page receives the `service` object via props
const { service } = Astro.props;
const { Content } = await service.render(); // Render the MDX content
---

<!-- 3. The page template uses the data -->
<BaseLayout title={service.data.title} description={service.data.summary}>
  <article>
    <h1>{service.data.title}</h1>
    
    <!-- Render the main body of the service page -->
    <Content />
  </article>
</BaseLayout>

Let's break down this code:

  1. getStaticPaths:
    • It calls getCollection('services') to fetch all your service entries.
    • It then uses .map() to iterate over each service.
    • For each one, it returns an object that tells Astro: "Create a page at /services/{service.slug} and pass the entire service object to it as a prop."
  2. Component Script:
    • const { service } = Astro.props; retrieves the specific service entry for this page. Since your getStaticPaths function returns one page for each service, Astro runs this template multiple times, each time with a different service prop.
    • await service.render() prepares the MDX content for rendering.
  3. HTML Template:
    • The entire page is wrapped in your BaseLayout, passing the service's title and a summary for the <head>.
    • Inside the layout, you display the service.data.title and render the service's main content using the <Content /> component.

This single file, [slug].astro, is now a factory for generating all your individual service pages.

Test your understanding!

Now, let's apply this to the blog you'll be building. Assuming you have a content collection named blog located in src/content/blog/ (or you can create a couple of sample .md files there now for testing).

  1. Create a new dynamic route file at src/pages/blog/[slug].astro.
  2. Inside this file, implement the getStaticPaths function to query your blog collection and generate a path for each entry. Use the entry's slug for the params and pass the entire entry object as props.
  3. In the component script section, retrieve the post from Astro.props.
  4. Use your BaseLayout to wrap the page content. Pass the post's title and a short description to the layout's props.
  5. Inside the layout, render the post's title in an <h1> tag and its body content using the rendered <Content /> component.

To test this, if you have a file at src/content/blog/my-first-post.md, you should be able to navigate to /blog/my-first-post in your browser and see the fully rendered page.

Show answer

src/content/blog/my-first-post.md (Example content file)

---
title: "My First Blog Post"
pubDate: 2024-08-15
description: "A short description of my first post for SEO purposes."
---

This is the content of my very first blog post. It's written in Markdown!

src/pages/blog/[slug].astro

---
import { getCollection, type CollectionEntry } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

interface Props {
  post: CollectionEntry<'blog'>;
}

const { post } = Astro.props;
const { Content } = await post.render();
---
<BaseLayout title={post.data.title} description={post.data.description}>
  <article>
    <h1>{post.data.title}</h1>
    <p>Published on: {post.data.pubDate.toDateString()}</p>
    <hr />
    <Content />
  </article>
</BaseLayout>

Conclusion

Fantastic work! You have just unlocked one of the most powerful features of static site generation. By combining dynamic routes with getStaticPaths, you can now build a scalable, content-driven architecture where your pages are generated automatically from your data.

Key Takeaways:

  • Dynamic Routes ([param].astro) act as templates for generating multiple pages.
  • getStaticPaths is the engine that drives this process; it runs at build time to define which pages to create.
  • It returns an array of objects, each defining the params (URL) and props (data) for a single page.
  • This pattern is fundamental for building blogs, documentation sites, e-commerce product pages, and—in your case—service and case study pages for an accounting website.

Up Next:
You now have individual pages for each blog post. But how would a visitor discover them? In the next lesson, we will build a blog listing page that queries all your posts from the collection and displays them as a list of links, creating the main entry point to your blog.