One Route, Many Pages

Creating a folder for every service would be tedious — and impossible if services are added dynamically. SvelteKit solves this with dynamic routes: one route definition that matches multiple URLs based on a parameter.


What You’ll Learn

  • Create dynamic routes with [parameter] syntax
  • Access route parameters in your component
  • Build service detail pages for BookIt

The Problem

BookIt has multiple services. Each needs its own page:

  • /services/lawn-mowing — Lawn Mowing details
  • /services/hedge-trimming — Hedge Trimming details
  • /services/garden-consultation — Garden Consultation details

Creating a separate folder for each service doesn’t scale. What happens when you have 50 services? 500?


The Solution: Dynamic Parameters

Instead of creating services/lawn-mowing/, services/hedge-trimming/, etc., you create a single folder with a parameter:

src/routes/services/[slug]/
└── +page.svelte

The square brackets [slug] tell SvelteKit this segment is dynamic. It will match any value:

  • /services/lawn-mowing
  • /services/hedge-trimming
  • /services/anything-at-all

The actual value (lawn-mowing, hedge-trimming, etc.) becomes available as a parameter in your component.


Create the Service Detail Route

Step 1: Create the Dynamic Folder

Inside src/routes/services/, create a folder named [slug]:

src/routes/
├── services/
│   ├── +page.svelte       (services listing)
│   └── [slug]/            ← Create this folder
│       └── +page.svelte   ← And this file

The folder name must include the square brackets. It’s literally named [slug].

Step 2: Access the Parameter

Create +page.svelte inside [slug]:

<!-- filename: src/routes/services/[slug]/+page.svelte -->
<script>
  let { data } = $props();
</script>

<h1>Service: {data.slug}</h1>
<p>You're viewing the detail page for: {data.slug}</p>

Wait — where does data.slug come from? Im getting ERROR on slug! Do not worry about it as we will to load it in next step.

Step 3: Add a Load Function

Dynamic routes typically need a load function to access the parameter. Create +page.js in the same folder:

// filename: src/routes/services/[slug]/+page.js
export function load({ params }) {
  return {
    slug: params.slug
  };
}

The params object contains all dynamic segments from the URL. Since our folder is [slug], we access params.slug. We will cover load function later durning the course.

Step 4: Test It

Visit these URLs:

  • http://localhost:5173/services/lawn-mowing
  • http://localhost:5173/services/hedge-trimming
  • http://localhost:5173/services/test-123

Each should display “Service: lawn-mowing”, “Service: hedge-trimming”, etc.


Build a Real Detail Page

Displaying just the slug isn’t useful. Let’s show actual service information. We need to simulate a data source. For now, we’ll hardcode service data again but now in in +page.js. Do not forget also return services from load function.

Update +page.js to find the service:

// filename: src/routes/services/[slug]/+page.js
const services = [
  {
    id: '1',
    slug: 'lawn-mowing',
    name: 'Lawn Mowing',
    description: 'Professional lawn mowing service for residential properties.',
    price: 50,
    duration: 60
  },
  {
    id: '2',
    slug: 'hedge-trimming',
    name: 'Hedge Trimming',
    description: 'Expert hedge and shrub trimming to keep your garden tidy.',
    price: 75,
    duration: 90
  },
  {
    id: '3',
    slug: 'garden-consultation',
    name: 'Garden Consultation',
    description: 'One-on-one consultation for garden planning and advice.',
    price: 100,
    duration: 60
  }
];

export function load({ params }) {
  const service = services.find(s => s.slug === params.slug);
  
  return {
    slug: params.slug,
    // Return the found service (or null if not found)
    service: service || null
  };
}

Now update the page to display service details:

<!-- filename: src/routes/services/[slug]/+page.svelte -->
<script>
  let { data } = $props();
</script>

{#if data.service}
  <h1>{data.service.name}</h1>
  <p>{data.service.description}</p>
  
  <dl>
    <dt>Price</dt>
    <dd>${data.service.price}</dd>
    
    <dt>Duration</dt>
    <dd>{data.service.duration} minutes</dd>
  </dl>
  
  <button>Book Now</button>
{:else}
  <h1>Service Not Found</h1>
  <p>Sorry, we couldn't find a service matching "{data.slug}".</p>
{/if}

Understanding the Data Flow

Here’s what happens when someone visits /services/lawn-mowing:

  1. SvelteKit matches the URL to services/[slug]/+page.svelte
  2. The load function in +page.js runs with params.slug = 'lawn-mowing'
  3. load finds the service and returns it
  4. The page component receives data.service via props
  5. The template renders the service details

This separation — loading data in +page.js, rendering in +page.svelte — is a core SvelteKit pattern we’ll explore deeply in Module 9.


Your Project Structure

src/routes/
├── +page.svelte              → /
├── services/
│   ├── +page.svelte          → /services
│   └── [slug]/
│       ├── +page.js          ← Load function (new!)
│       └── +page.svelte      → /services/* (dynamic)
├── about/
│   └── +page.svelte          → /about
└── contact/
    └── +page.svelte          → /contact

Common Mistakes

Forgetting the Square Brackets

src/routes/services/
└── slug/
    └── +page.svelte    → /services/slug (literal!)

Without brackets, slug is treated as a literal path segment. Only /services/slug would match — not /services/lawn-mowing.

src/routes/services/
└── [slug]/
    └── +page.svelte    → /services/* (any value)

Parameter Name Mismatch

// Folder is [slug], but accessing wrong parameter name
export function load({ params }) {
  return { id: params.id };  // ❌ undefined — should be params.slug
}

The parameter name in params matches the folder name. [slug] gives you params.slug, [id] would give you params.id.

Not Handling Missing Data

Always handle the case where no service matches:

{#if data.service}
  <!-- Show service -->
{:else}
  <!-- Show error state -->
{/if}

Without this check, visiting /services/nonexistent would crash trying to access properties on undefined.


Summary

Dynamic routes let you create a single page definition that handles many URLs. The [parameter] folder syntax captures the URL segment as a value you can use to load specific data.

Key takeaways:

  • Square brackets [name] create dynamic route segments
  • Access parameters via params.name in load functions
  • Always handle cases where the parameter doesn’t match valid data

Next Steps

We have multiple pages now, but no way to navigate between them without typing URLs. Continue with Link All Pages Together to add proper navigation throughout BookIt.