svelte-spa-router

repository·main·Indexed 23 days ago

https://github.com/italypaleale/svelte-spa-router

A lightweight, hash-based router for Svelte 5 Single Page Applications (SPAs). It is optimized for static servers, requiring no server-side configuration for deep linking. Key features include regex-based route parsing via regexparam, support for dynamic and optional parameters, nested routing, and scroll position restoration. It provides lifecycle callbacks like onRouteLoading and onRouteLoaded, and a wrap method for advanced configurations such as route guards and code-splitting.

Tokens
11.7K
Snippets
32
Records
51
Agent score
81%

What's inside svelte-spa-router

  1. Overview of svelte-spa-router

    main

    What is svelte-spa-router?

    svelte-spa-router is a lightweight, hash-based router specifically optimized for Svelte 5 Single Page Applications (SPAs).

    Key Features

    • Hash-based routing: Uses the URL fragment (the part after #) for navigation. This is ideal for static SPAs because it does not require any server-side configuration to handle deep links or page refreshes.
    • Minimal footprint: Designed to be simple and lightweight.
    • Regex-based route parsing: Uses regexparam to support route parameters (e.g., /book/:id?).

    Version Compatibility

    • Svelte 5: Use the current version.
    • Svelte 3 and 4: Use the v4 branch instead.
  2. Untitled record

    main

    svelte-spa-router is a hash-based router specifically optimized for Svelte 5 Single Page Applications (SPAs). It uses the regexparam library for route parsing, allowing for parameters like /book/:id?.

    Key features:

    • Hash-based routing: Navigation occurs after the # in the URL (e.g., http://example.com/#/profile), which is ideal for static SPAs as it requires no server-side configuration for deep linking or page refreshes.
    • Minimal footprint: Designed to be simple and lightweight.
    • Parameter support: Supports dynamic route segments and optional parameters.

    Note: For Svelte 3 and 4, use the v4 branch of the repository.

  3. Understand Hash-based routing

    main

    How Hash-based routing works

    In hash-based routing, the current view is stored in the URL after the # symbol (the hash or fragment).

    Example URLs:

    • http://example.com/#/profile
    • http://example.com/#/book/42

    Why use Hash-based routing over HTML5 History API?

    While the HTML5 History API produces cleaner URLs (e.g., http://example.com/profile), it requires a backend server to intercept and process requests for those paths to ensure that refreshing the page doesn't result in a 404 error.

    Benefits of Hash-based routing:

    • No server configuration required: Works perfectly with static file hosting.
    • Simpler deployment: Ideal for fully-static SPAs.
    • Reliable navigation: Users can share links and refresh pages without needing a specialized server setup.

    Note: This approach is best suited for applications where SEO is not a primary concern, such as apps behind an authentication wall.

  4. Add custom user data to routes

    main

    You can attach an optional userData dictionary to a route via wrap. This data is passed to:

    • All pre-condition functions (as detail.userData)
    • The onRouteLoading callback
    • The onRouteLoaded callback
    • The onConditionsFailed callback

    This is useful for passing context or custom callbacks that your route event handlers can use to perform specific actions.

    const routes = {
       '/books': wrap({
          component: Books,
          userData: {foo: 'bar'}
       }),
       '/authors': wrap({
          asyncComponent: () => import('./Authors.svelte'),
          userData: {hello: 'world'}
       })
    }
  5. Enable code splitting with dynamic imports

    main

    Starting with version 3.0, svelte-spa-router supports code splitting by using the import() construct for components. This allows bundlers like Vite, Rollup, and Webpack to split your application into smaller chunks, reducing the initial bundle size.

    To implement this, you must use the wrap method from svelte-spa-router/wrap. In your route configuration, instead of passing the component directly, pass a wrap object where the asyncComponent property is a function definition that returns the dynamic import.

    Warning: Do not invoke the import immediately. Use asyncComponent: () => import('./Component.svelte') instead of asyncComponent: import('./Component.svelte').

    import {wrap} from 'svelte-spa-router/wrap'
    
    const routes = {
        '/': Home,
    
        // Wrapping the Author component
        '/author/:first/:last?': wrap({
            asyncComponent: () => import('./routes/Author.svelte')
        }),
    
        // Wrapping the Book component
        '/book/*': wrap({
            asyncComponent: () => import('./routes/Book.svelte')
        }),
    
        // Catch-all route last
        '*': NotFound,
    }
  6. Untitled record

    main

    To run the provided examples from the repository root, follow these steps:

    1. Install dependencies and build the package:
    pnpm install
    pnpm run build
    1. Navigate to a specific sample directory (e.g., basic-routing):
    cd examples/basic-routing
    1. Install sample dependencies and start the development server:
    pnpm install
    pnpm run dev

    The sample will typically be available at http://localhost:5173.

    pnpm install
    pnpm run build
    
    cd examples/basic-routing
    
    pnpm install
    pnpm run dev
  7. Mark links as active using the `use:active` action

    main

    svelte-spa-router provides a use:active action to automatically apply CSS classes to links based on the current route. This is useful for styling navigation menus where the current page link needs to look different.

    You can use the action in three ways:

    1. With a configuration object: Specify a custom path (supporting wildcards or regex), a className for active states, and an inactiveClassName for non-active states.
    2. With a shorthand string/regex: Passing a single string or regular expression will be treated as the options.path.
    3. With no arguments: Uses the link's href as the path and the default active class name.

    Note on Styling: Because the router adds the class directly to the element, you must use the :global() modifier in your Svelte <style> block to ensure the styles are applied to the link.

    <script>
    import {link} from 'svelte-spa-router'
    import active from 'svelte-spa-router/active'
    </script>
    
    <style>
    /* Style for "active" links. 
    Need to mark this :global because the router adds the class directly */
    :global(a.active) {
        color: red;
    }
    </style>
    
    <a href="/hello/user"
      use:link
      use:active={{path: '/hello/*', className: 'active', inactiveClassName: 'inactive'}}>
      Say hi!
    </a>
  8. Upgrade to svelte-spa-router 3.x

    main

    Upgrading from 2.x to 3.x involves two main changes:

    1. Automatic URL Parameter Decoding: URL parameters are now automatically decoded using decodeURIComponent. If your application manually decoded parameters before, you should remove that logic.
    2. New wrap Method Signature: The wrap method exported from the main package is deprecated. You must now import it from svelte-spa-router/wrap. The new signature accepts a single configuration object instead of multiple positional arguments.
    // New import path
    import {wrap} from 'svelte-spa-router/wrap'
    
    const routes = {
        '/foo': wrap({
            // Component
            component: Foo,
            // Custom data
            customData: {foo: 'bar'},
            // Pre-condition function
            conditions: [
                (detail) => {
                    // ...
                },
            ]
        })
    }
  9. Implement route pre-conditions (route guards)

    main

    Route pre-conditions (or "route guards") are functions executed before a route is loaded. They are defined in the options.conditions array within wrap.

    Execution Logic:

    • Functions are executed in order.
    • If a function returns true, the router proceeds to the next condition or loads the route.
    • If a function returns false, the router stops and does not load the route.
    • Pre-conditions can be async (e.g., for checking authentication via a network request).

    The detail object: Each pre-condition receives a detail object containing:

    • detail.route: The matched route definition.
    • detail.location: The current path.
    • detail.querystring: The current querystring.
    • detail.userData: The custom data attached via wrap.
    <!-- App.svelte -->
    <Router {routes} {onConditionsFailed} />
    
    <script>
    import Router from 'svelte-spa-router'
    import {wrap} from 'svelte-spa-router/wrap'
    import Lucky from './Lucky.svelte'
    
    const routes = {
       '/lucky': wrap({
          component: Lucky,
          userData: { hello: 'world' },
          conditions: [
             (detail) => {
                return (Math.random() > 0.5)
             },
             async (detail) => {
                const response = await fetch('/user/profile')
                const data = await response.json()
                return data.isAdmin
             }
          ]
       })
    }
    
    function onConditionsFailed(detail) {
       console.error('onConditionsFailed', detail)
       if (detail.userData.hello === 'world') {
          // Perform fallback action
       }
    }
    </script>
  10. Parse the querystring into an object

    main

    To use querystring values as a dictionary/object in your application, you must parse the router.querystring string.

    1. Modern Browsers: Use the native URLSearchParams API.
    2. Advanced/Legacy Support: Use the qs library if you need to support older browsers or require advanced parsing features like nested objects and arrays.

    When using Svelte 5, it is recommended to use the $derived rune to ensure your parsed object updates automatically whenever router.querystring changes.

    <script>
    import {parse} from 'qs'
    import {router} from 'svelte-spa-router'
    
    // Use a derived to ensure parsed is updated
    // every time router.querystring changes
    const parsed = $derived(parse(router.querystring || ''))
    </script>
    <code>{JSON.stringify(parsed)}</code>