Pages, Layouts, and Styles

Routing and Dynamic Pages


Learning Objectives

  • You know of SvelteKit’s file-based routing.
  • You know how to create routes with dynamic parameters.
  • You know how to expose dynamic parameters from the server.

When server-side rendering of the client-side application is enabled, there exists a server that is serving the client-side content. This is separate from the server that, in our walking skeleton, is responsible for API functionality and for interacting with the database.

That is, there are two servers: one for the client-side application and one for the server-side application.

Having a server for the client-side application allows additional processing of the request before the client-side application is served. This is useful especially for creating pages dynamically based on the request.

Allow server-side rendering

When working on the subsequent exercises, make sure that server-side rendering is enabled. That is, there should not be a file +layout.server.js in the src/routes folder with the line export const ssr = false;.

File-based routing

SvelteKit uses file-based routing, where files and folders in the src/routes folder define the routes of the client-side application. As an example, a path /books in the application would correspond to a +page.svelte file in the folder src/routes/books.

There are a handful of special files that are used to define the routes. The +page.svelte acts as the entrypoint to the path indicated by the folder, and +layout.svelte is used to define the layout of the page. The layout file can be placed in the same folder as the +page.svelte file, or in a parent folder — often, an application has a layout at least in the src/routes folder.

Routes with dynamic parameters

In addition to the two above files that we have already used, it is possible to create routes with dynamic parameters, such as /books/1 or /books/5. Dynamic parameters are defined by creating a folder where the name is enclosed in square brackets, such as [book]. The name inside the brackets is used as the name of the parameter (we’ll look into using the parameter in a moment).

For example, to create a route that corresponds to any path under /books, create a folder called [book] in the src/routes/books folder. In this folder, create a file called +page.svelte, and place the following content to the file.

<p>one book!</p>

Now, when you open up the application and navigate to the path /books/1, you should see the text “one book!”. Similarly, when you access the path /books/5, you should see the text “one book!”.

A path can have multiple dynamic parameters, such as /books/1/chapters/2. In this case, the file-based routing folder structure would be src/routes/books/[book]/chapters/[chapter], where the [chapter] folder would contain a +page.svelte file.

Loading Exercise...

Server-side dynamic parameters

As the routes with dynamic parameters are handled on the server responsible for serving the client-side application, instructions on how to handle the dynamic parameters are needed on the server. These instructions are defined in a file called +page.js, which contains code that is run both on the client and the server.

The file +page.js should export a function called load, which returns data that is then passed to the page component. As an example, create a file +page.js and place it to the folder src/routes/books/[book] (with the +page.svelte that shows the text “one book!”). Place the following content to the file.

export function load({ params }) {
  return params;
}

Now, the +page.svelte file can access the dynamic parameter through a property called data. Modify the src/routes/books/[book]/+page.svelte file to the following.

<script>
  let { data } = $props();
</script>

<p>book {data.book}</p>

With this in place, when you access the path /books/1, you should see the text “book 1”. Similarly, when you access the path /books/5, you should see the text “book 5”, and so on.

If you would wish that the code in +page.js would only run on the server, the file can also be named as +page.server.js. In that case, the code in the file would only run on the server.

Loading Exercise...

Data from client-side server

It is also possible to store data on the server responsible for the client-side application. As an example, we could have a list of books that we would like to show on the page, depending on the request. The list of books could be stored in and exported from a file called books.js in the src/routes/books folder.

export const books  = [
  { id: 1, title: "Philosopher's Stone" },
  { id: 2, title: "Chamber of Secrets" },
  { id: 3, title: "Prisoner of Azkaban" }
];

To access the list of books in the +page.js file, we would import the file and, e.g., add the book to the data that goes to the page component. As an example, modify the src/routes/books/[book]/+page.js file to the following.

import { books } from "./books.js";

export function load({ params }) {
  return {
    ...params,
    foundBook: books.find(book => book.id == params.book)
  };
}

With this, the +page.svelte file can access the book through the data property in addition to having information about the dynamic parameter. Modify the src/routes/books/[book]/+page.svelte file to the following.

<script>
  let { data } = $props();
</script>

<p>Book identifier from path: {data.book}</p>
<p>Book from dataset: {data.foundBook?.title}</p>

We use the optional chaining operator ?. to access the title property of the book, as the foundBook property might not exist if the book is not found in the dataset.

Loading Exercise...

Dynamic parameters and APIs

On the client, we can also use the parameters for retrieving data from an API. As an example, if we would have an API endpoint /books/:id similar to the one created in the chapter on Repository and CRUD Pattern, we could retrieve the individual books from the API.

In such a case, we would modify the +page.js to provide the parameters to the client-side component and use the parameters to fetch the data from the API. As an example, modify the src/routes/books/[book]/+page.js file to the following.

export function load({ params }) {
  return params;
}

And, modify the src/routes/books/[book]/+page.svelte file to the following.

<script>
  import { PUBLIC_API_URL } from "$env/static/public";

  let { data } = $props();
  let book = $state({});

  const getBook = async () => {
    const response = await fetch(`${PUBLIC_API_URL}/books/${data.book}`);
    book = await response.json();
  }

  $effect(() => {
    getBook();
  });
</script>

<p>Book title: {book?.title}</p>

Now, when the user opens up the site using a path such as /books/1, the client-side component fetches the book with the identifier 1 from the API and shows the title of the book.

Loading Exercise...

It is also possible to retrieve the data on the server responsible for the client-side application and then pass the retrieved data to the client-side component for rendering. That is, the above example could also be written so that the +page.js would be as follows.

import { PUBLIC_API_URL } from "$env/static/public";

export async function load({ params }) {
  const response = await fetch(`${PUBLIC_API_URL}/books/${params.book}`);
  const book = await response.json();
  return book;
}

And the page component would be as follows.

<script>
  let { data } = $props();
</script>

<p>Book title: {data.book?.title}</p>
Not going there

Although the above would be sensible to do in many cases, we are not going to do it in the exercises in this course. This is to primarily emphasize the separation of concerns between the client-side and server-side applications.

When working with client-side functionality, the exercises (and the tests) expect that API requests to the server-side API are made from the client-side component and not from the client-side server. This changes when we start looking into form actions — even there, follow the instructions in the exercises.