Single-page applications (SPAs) provide user experiences that mirror those on desktop/mobile applications. Navigating from one page to the next no longer requires waiting for the browser to fully refresh the page on each transition, which keeps users engaged and productive. Because only a single page is served to a user, only certain sections of the view are dynamically updated by the browser (rather than the entire view via a full-page refresh) when the user navigates to a new page. To coordinate which sections of the view are dynamically updated for each route a user can navigate to, the single-page application must leverage a router to map these updates to each possible URL and render accordingly.
When a router is added to an application powered by a modern front-end library/framework, such as React.js and Svelte, developers are able to quickly build single-page applications due to the declarative APIs offered by these libraries/frameworks.
Compared to applications built with React.js, those built with Svelte are smaller (in bundle size) and faster. Svelte's compiler strips out the runtime overhead from outputted bundles and only ships the code that's needed. Additionally, Svelte does not rely on a virtual DOM and diffing algorithms (or other such techniques) for rendering updates. Instead, Svelte's compiler can determine ahead of time which parts of the application should change for a given state change, and when such state changes occur, it will surgically modify the DOM directly.
While not as large as the React.js ecosystem, the Svelte ecosystem continues to grow and gain traction. When considering all of the routers in the React.js ecosystem, there are two that are often chosen for their maturity, robustness and simple-to-use APIs:
To introduce the concept of integrating a router into a Svelte application, we will use an "easy to start with" router:
Using this router and Svelte, we will be building the following dashboard as a single-page application:
To start, download this project from GitHub:
This repository was scaffolded from the
sveltejs/template project template, and it will contain the all of the dashboard's components and views. We will integrate
svelte-router-spa into this dashboard so it can...
Tie these components and views together.
Update certain content (in this case, the title bar and page view) as a user navigates from one page to the next.
svelte-router-spa as a dependency.
To run the application, execute the
localhost:5000 in a browser. As you hover over each navigation bar item, notice that each item has an
href value of
/. Our objective will be to make these items link to their correct corresponding routes and render the correct page view.
To preview any of the other page views, go to the
src/layouts/DashboardLayout.svelte and swap out the current
<Home /> view with another view.
Example: Preview the
<Projects /> view.
The browser should automatically refresh the application to reflect this change via hot reloading. Note that the title bar does not update to "Dashboard > Project" when the current view is
<Projects />. This also needs to be fixed.
This dashboard will support the following routes:
Integrating the Router#
Inside of the
src directory, add a
routes.js file. Inside of this file, let's define the dashboard routes based on the information in the table above:
Each object in
routes represents an individual route. For each route, a URL pathname (
name) is mapped to a Svelte component (directly via
layout, or indirectly via
name- The URL pathname of a route.
redirectTo- A URL (or URL pathname) of a route to redirect to when visiting the route the
redirectTois defined within. In this case, visiting
/will redirect the user to
/dashboard, which will redirect the user to
component- A component that is rendered when the route is active.
layout- A component that is section of the application and is shared across multiple pages. It contains a child component defined within a nested route.
nestedRoutes- An array of routes with URL pathnames prefixed by a parent route's
name. In this case, the component
<Projects />for nested route "projects" would be rendered when the user visits the route
nameof the nested route's parent). When the
nameof a nested route is set to "index," the redirect or rendering of a
layout) defined in this nested route will occur when its parent route is visited. In this case, visiting
/dashboardwill redirect the user to
Next, let's add the
<Router /> component to
src/App.svelte to indicate the top-most area in the application where updates will be made to the application when these routes are visited. Anything outside of
<Router /> will not be affected. The layout (or component) that matches the current active route will be rendered inside of
<Router />. In this case, when visiting
/dashboard/home, etc.), the
<DashboardLayout /> will be rendered where
<Router /> is placed.
<DashboardLayout /> is a layout component, it needs to define a place to render the child components defined in the nested routes of
/dashboard. Let's add a
<Route /> component to handle this. To render the correct child component for the current route,
<Route /> must be passed information about the current route (and child routes) via the
currentRoute object. This object is passed to the child components it renders.
In the browser, visit
localhost:5000. Notice the redirection to
localhost:5000/dashboard/home and the
<Home /> view being rendered for this route. Now visit
<Projects /> view is rendered for this route.
Fixing the Navigation Bar#
There are currently two issues with the navigation bar:
hrefvalue of each navigation bar item is incorrect. For example, when a user clicks on the "Projects" item, the browser's address bar should be updated to
localhost:5000/dashboard/projects, and the browser should load the
The navigation bar item that corresponds to the current active route is not highlighted. For example, if the current active route is
localhost:5000/dashboard/home, then the colors of the text and background of the "Home" item should be changed to reflect that the user is currently visiting its route.
First, let's pass
currentRoute to the
<Navbar /> component in the
<DashboardLayout /> layout component:
Next, let's propagate
currentRoute to the
<NavbarMenu /> component in the
<Navbar /> component:
With the removal of the
dashboardRoutes prop, we will need to directly import the list of routes into the
<NavbarMenu /> component to render the navigation items. Currently,
routes is not formatted properly for this task. We will need to refactor the
routes.js file to export a
dashboardRoutes array, which will be used only to render these navigation items, and add a custom flag (
isExcludedFromNav) to the "projects/:id" nested route object to tell
<NavbarMenu /> to not render it as an item in the navigation bar.
Additionally, we will need to pass the
currentRoute object to the
<NavbarMenuItem /> component.
Inside of the
<NavbarMenuItem /> component, the
isActive flag determines whether or not to highlight the navigation item when its corresponding route is the current active route.
svelte-router-spa provides a useful method for determining if a path is the current active route:
routeIsActive. It accepts two arguments:
A boolean. If
true, then consider the pathname to be active if it matches the current route exactly (not partially). If
false, then vice-versa.
In this case, we would want the match to be exact.
currentRoute has been passed to the
<NavbarMenuItem /> component to trigger a reactive statement that will set the
isActive flag whenever
currentRoute changes. If the user clicks a navigation item, then the active status of the item should be changed to reflect the new current route. Hence, the following statement will correctly set the
isActive flag whenever any of the navigation items is clicked:
Alternatively, you could substitute the
<a /> element with the
<Navigate /> component (a wrapper around an
<a /> element) from
svelte-router-spa to automatically add an
active class if the route specified for
to is the current route.
to- A valid, navigable route (similar to
styles- The classes to be applied to the underlying
title- The title to be applied to the underlying
All-in-all, the following changes to the
<NavbarMenuItem /> component are the most comprehensive:
Verify that the navigation items work properly.
Verify that the
active class automatically being set as a result of using the
<Navigate /> component.
Extra Challenge: Make these same adjustments to the mobile navigation bar, which can be viewed when the browser width is less than 768px. These are the files that need to be modified:
Syncing the Title Bar with the Current Route#
Notice that the title bar is not updated to reflect the current route as you navigate from one page to the next. Unfortunately, the
currentRoute object does not provide a route label.
Let's export a new method,
src/routes.js to retrieve the current route's label.
namedParamis the segment of the URL pathname prefixed with a
:in the route
name. For example,
idwould be a
routesMapping.withoutParams is an object that maps a route's pathname to its label. For example,
/dashboard/projects is mapped to "Projects."
For routes without
getRouteLabel will check
routesMapping.withoutParams for an exact pathname match and return a label for the matched pathname. For routes with
getRouteLabel will check an array of
[ regular expression, label ] pairs and test the pathname against each pair's regular expression. The label of the first test that evaluates to
true will be returned. If no matching routes are found, then an empty string is returned as the label.
getRouteLabel into the
<TitleBar /> component, and set its value to
currentRouteLabel via a reactive statement. This ensures the value of
currentRouteLabel is updated whenever
currentRoute has changed.
Verify that the title bar is updated whenever a navigation bar item is clicked.
Update Label in Views with
Let's apply the same reactive statement to each view component to render the correct label in the placeholder content.