Svelte Single-Page Applications with Svelte Router SPA

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: @reach/router and react-router . To introduce the concept of integrating a router into a Svelte application, we will use an "easy to start with" router: svelte-router-spa . 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... Install svelte-router-spa as a dependency. To run the application, execute the dev script: Visit 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. ( src/layouts/DashboardLayout.svelte ) 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: 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: ( src/routes.js ) Each object in routes represents an individual route. For each route, a URL pathname ( name ) is mapped to a Svelte component (directly via component or layout , or indirectly via redirectTo ). 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 (or /dashboard/home , etc.), the <DashboardLayout /> will be rendered where <Router /> is placed. ( src/App.svelte ) Since <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. ( src/layouts/DashboardLayout.svelte ) 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 localhost:5000/dashboard/projects . The <Projects /> view is rendered for this route. There are currently two issues with the navigation bar: First, let's pass currentRoute to the <Navbar /> component in the <DashboardLayout /> layout component: ( src/layouts/DashboardLayout.svelte ) Next, let's propagate currentRoute to the <NavbarMenu /> component in the <Navbar /> component: ( src/components/Navbar.svelte ) 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. ( src/routes.js ) Additionally, we will need to pass the currentRoute object to the <NavbarMenuItem /> component. ( src/components/NavbarMenu.svelte ) 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: In this case, we would want the match to be exact. The 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. All-in-all, the following changes to the <NavbarMenuItem /> component are the most comprehensive: ( src/components/NavbarMenuItem ) 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: src/components/NavbarMobileMenu.svelte , src/components/NavbarMobileMenuItem.svelte and src/components/Navbar.svelte . 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, getRouteLabel from src/routes.js to retrieve the current route's label. 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 namedParams , getRouteLabel will check routesMapping.withoutParams for an exact pathname match and return a label for the matched pathname. For routes with namedParams , 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. Import 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. ( src/components/TitleBar.svelte ) Verify that the title bar is updated whenever a navigation bar item is clicked. Let's apply the same reactive statement to each view component to render the correct label in the placeholder content. ( src/views/Board.svelte , src/views/Calendar.svelte , ..., src/views/Timeline.svelte ) Verify that the correct label is rendered. Currently, the "Edit" link for a project listed under /dashboard/projects loads the "Home" view at /dashboard/home . Let's modify the link to load the "Project Info" view at /dashboard/projects/<id> . First, pass the currentRoute object to the <ProjectsTable /> component: ( src/views/Projects.svelte ) Inside of the <ProjectsTable /> component, swap out the <a /> element for the <Navigate /> element. Prefix the link with currentRoute.path . ( src/components/ProjectsTable.svelte ) Verify that clicking on "Edit" loads the "Project Info" view. Inside of package.json , there is a start script to run the production-optimized application. ( package.json ) It uses sirv , which spins up a server to allow you to preview the assets in a specified directory (in this case, public ). Passing the -s option to sirv tells it to treat the public directory as a single-page application. When you refresh the page at a route defined by the router, the route is unknown to sirv , so it will automatically serve the index.html file, which knows how to handle this route. Without this option, this will be loaded in your browser: Inside of routes.js , check if any of the defined routes has layout or component set to a string instead of an imported component. Example : ( src/routes.js ) Notice DashboardLayout is wrapped in quotes. Remove those quotes. Inside of rollup.config.js , pass the following options to the commonjs plugin: ( rollup.config.js ) Experiment! Visit the svelte-router-spa documentation and try adding protected routes to the project. If you want to learn more about Svelte, then check out Fullstack Svelte :

Thumbnail Image of Tutorial Svelte Single-Page Applications with Svelte Router SPA