7

Advanced Routing Techniques

With everything we’ve covered so far, you could set this book down now and go build a simple website using SvelteKit. But if you wanted to build more advanced functionality into your application, you might find yourself struggling to come up with the proper hierarchies for routes. That’s because when it comes to dynamic routing, we’ve only scratched the surface. In Chapter 4, we discussed creating dynamic pages with parameters passed to our routes. In that example, we loaded articles by using the provided slug and matching it with those found in our demonstration database. We had no way of knowing what the slug would be ahead of time and it would have been needlessly complicated to create a new route for each article. Instead, we looked at the slug parameter that was received based on the URL being accessed.

This was only a brief introduction to dynamic routing. In this chapter, we’ll look at some more advanced techniques that can help you supercharge your routing logic. We’ll examine routing with optional parameters, parameters of unknown lengths, how to match parameters with regular expressions, which routes will take precedence in instances of routing logic collisions, and more advanced layout techniques, including methodologies for breaking out of them.

This chapter will be broken into the following topics:

  • Using optional parameters
  • Rest parameters
  • Matching, sorting, and encoding – oh, my!
  • Advanced layouts

By the end of this chapter, you will have a mastery of the various routing techniques available to you in SvelteKit. No matter your next SvelteKit project’s requirements, you will have the knowledge required for solving and tackling any complex routing dilemmas.

Technical requirements

The complete code for this chapter is available on GitHub at: https://github.com/PacktPublishing/SvelteKit-Up-and-Running/tree/main/chapters/chapter07

Using optional parameters

Since we teased optional parameters in the Creating Dynamic Pages section of Chapter 4, let’s start there. When creating optional parameters in a route, there are some things to keep in mind. For instance, they cannot exist alongside another route as this would cause a collision in the routing rules. When creating an optional route, it works best for instances where the final portion of the route can have a default option. Many applications will change the URL based on a language selected by the user. For our example, we’ll illustrate how to create an optional parameter by selecting a country in North America that our demonstration store operates in. We won’t actually build an entire store but rather use it to illustrate the advanced routing concepts in this chapter.

To begin, let’s create a new route in our navigation just like we have for previous examples:

src/lib/Nav.svelte

<nav>
  <ul>
    <li><a href='/'>Home</a></li>
    ...
    <li><a href='/login'>Login</a></li>
    <li><a href='/store'>Store</a></li>
  </ul>
</nav>

All we need to do is add another list item with a link to our new route in the navigation menu. After doing that, we can create the store directory, which is where all of the examples for this chapter will exist:

src/routes/store/+layout.svelte

<h2>Store</h2>
<ul>
  <li><a href="/store/locations/">Locations</a></li>
  <li><a href="/store/products/">Products</a></li>
</ul>
<slot />

This simple layout will allow us to navigate the various concepts covered in this chapter. After adding links, we use the Svelte <slot> element referenced in earlier chapters. Take a moment to go and create all of the necessary directories. Next, we’ll also create a simple landing page for the /store route:

src/routes/store/+page.svelte

<h3>Welcome to the Store!</h3>

Having created the files necessary for the /store route as well as the locations directory, we’ll now create yet another directory. The major difference with this one will be that it uses double square brackets ([[country]]) in the name. This is how SvelteKit differentiates optional routes from those that are not. Because we’re creating a page with an optional parameter, we don’t need to create a +page.svelte inside of the locations directory. Rather, we’ll add that inside of the [[country]] directory. To proceed, we’ll create the appropriate +page.svelte and +page.js files:

src/routes/store/locations/[[country]]/+page.svelte

<script>
  export let data;
</script>
<h2>You're viewing the {data.country.toUpperCase()} store.</h2>
<ul>
  <li><a href="/store/locations">North America</a></li>
  <li><a href="/store/locations/ca">Canada</a></li>
  <li><a href="/store/locations/me">Mexico</a></li>
  <li><a href="/store/locations/us">United States</a></li>
</ul>

By now, this should look all too familiar. We use export let data; so that we may access the information provided by load() in the next file. We use that data to inform the user which country’s store locations they are viewing and display the abbreviation in uppercase. We then create an unordered list populated with links to the various allowed routes we will provide in the next file.

In [[country]]/+page.js, we need to check the provided parameters of the route against the list of countries our store operates in. We can do so with the following code:

src/routes/store/locations/[[country]]/+page.js

export function load({ params }) {
  const codes = [
    'na',
    'ca',
    'me',
    'us'
  ];
  const found = codes.filter(country_code => country_code === params.country);
  return {country: found[0] ?? 'na'};
}

Exporting load() functions should also feel familiar to you at this point. In this particular function, we only need access to params and so we destructure the RequestEvent object passed to load(). We’ve then declared a codes array that works as a list of approved routes. On the next line, we check if the provided route is in the array of approved routes by running filter() on codes. Then filter() returns an array containing all of the matches and assigns it to the found constant. We can then return an object containing the country property, which is assigned the first value inside of found. If the first value of found is empty, we’ll default to the value that shows all of North America. In this case, na.

Once we’ve done all of this, we can open our application, click store, click locations, and view the various North American countries our store operates in. When clicking each of the options from the unordered list, notice how the text on our page changes as well as the route in the URL. When we select North America, the text shown reflects the abbreviation NA due to the ternary logic used in the return of load(). When selecting any of the other options, the abbreviation is updated accordingly. As mentioned previously, optional parameters work best when the final section of the route can have a default option. If the optional parameter were to be included somewhere in the middle of the route, then any subsequent portions of the route would be understood by the routing mechanism as the optional parameter.

In this example, we created a new route using double square brackets [[ ]]. While this example has a long way to go before functioning as a complete store, it should illuminate how to use optional parameters in routes. Now that you understand optional parameters, let’s see how we can wrangle routes of unknown lengths.

Rest parameters

Just as JavaScript functions can accept rest parameters using the rest operator (), so too can our routes. By using a rest operator inside of single square brackets, we can allow a variable length on the specified route. This feature comes in handy when creating something like a file browser where the URL should match a path that then makes the page content shareable via the URL.

To see this concept in action, let’s create a products route in our store. Start by adding src/routes/store/products/+layout.svelte so that we may navigate products easily:

src/routes/store/products/+layout.svelte

<h3>Products</h3>
<ul>
  <li><a href="/store/products/shirts">Shirts</a></li>
  <li><a href="/store/products/shirts/mens">Mens Shirts</a></li>
  <li><a href="/store/products/shirts/mens/tshirts">Men's T-shirts
    </a></li>
  <li><a href="/store/products/shirts/mens/tshirts/cotton">Men's 
    Cotton T-shirts</a></li>
  <li><a href="/store/products/shirts/mens/tshirts/cotton/
    graphic">Men's Graphic Cotton T-shirts</a></li>
</ul>
<slot />

This Svelte component is rather simple. It consists of a title, an unordered list, list items, and links to various products. Again, we’ve used the Svelte <slot /> element to keep the navigation on our page as we click around. Next, let’s create an endpoint that can handle the varying lengths of products we’ve just provided. To do so, we’ll create a folder using square brackets and prefix the directory name with the rest operator. For this example, we’ll use [...details] as the directory name. Let’s look at the +page.js and +page.svelte files now:

src/routes/store/products/[...details]/+page.js

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

Since we’re not building out an entire store, we can keep this one incredibly simple. As we’re attempting to showcase how rest parameters work within SvelteKit’s routing mechanism, we’ll simply return params from load(). A more robust and practical example might take the value from params and use it to filter a list of products retrieved from the database. That data could then be returned from load() for each product to be rendered in the next file.

Now, to show how the rest parameter value changes, we’ll add the following +page.svelte:

src/routes/store/products/[...details]/+page.svelte

<script>
  export let data;
</script>
<h4>Product page</h4>
{#if data.details}
  <p class='red'>{data.details}</p>
{:else}
  <p>No product selected! Try clicking one or adding your own URL.
{/if}
<style>
  .red {
    color: red;
    font-weight: bold;
  }
</style>

Again, we’re keeping things simple. Instead of showing all of the products that could be available in data, we’re simply using the Svelte {#if} and {:else} directives to demonstrate how the details parameter changes. If data.details is empty, we show a default message. If it has a value, we show it in bold red text. Had we given the directory a different name, that name would be how we accessed the parameter. Try clicking some of the links to the various products and notice how the URL changes in the browser but so too does the value in red. What happens if you add your own values to the URL after /store/products/?

With these advanced routing techniques, we have to consider some implications. For instance, just as optional parameters work best when at the end section of the URL, rest parameters cannot be followed by an optional parameter. If we attempt to provide optional routing sections after the rest parameter, they will be consumed by the rest parameter. To see the error thrown by the Vite development server, try creating an optional directory inside of /[...details]/. You won’t have to worry about accidentally doing this since Vite will be watching out for you, but it’s still good to know about it when planning routes for your application.

If you find yourself building routes of an unknown length into your application, consider creating them using SvelteKit’s rest parameters. Not only do they handle those indeterminate lengths, but the logic is easily incorporated into the existing flow of SvelteKit apps.

Matching, sorting, and encoding – oh, my!

If you’re unfamiliar with the ins and outs of SvelteKit’s more advanced routing techniques, it can quickly become unwieldy. To get ahead of the unexpected, we’re going to look at a few more strategies you can use to ensure your application’s routing works as you intend it to. In this section, we will cover how you can ensure that parameters are of the type you’re expecting them to be. We’ll then examine how SvelteKit handles URLs that can resolve to multiple routes. We’ll wrap it up with a bit of information about encoding URLs. You can expect to see the following sub-sections:

  • Matching
  • Sorting
  • Encoding

Once finished, you’ll be one step closer to mastering the routing of SvelteKit apps.

Matching

We’ve looked at how we can use optional and rest parameters in our routes. But think back to the example we created in Chapter 4 dealing with dynamic routes. In the news section, we only checked whether the provided [slug] parameter existed in our database. If we wanted to ensure that the value being passed to our database was in fact a slug, we could create a custom matcher to do just that.

To create a matcher with SvelteKit, we add a JS file with a descriptive name to src/params/. If the directory doesn’t exist yet, don’t fret! You can simply go ahead and create it now. The files here export a single function: match(). That function accepts one string parameter and returns a boolean value. Because the value passed to the function is a string, we’ll use regular expressions (regex) to ensure the parameter passed in is of the type we want enforced on our route. Regex may seem daunting at first but there exist plenty of tools online to assist in the creation and learning of regex rules. See the end of this chapter for more resources. Now let’s create a matcher for our news articles to ensure they are being passed a proper slug before we perform our database lookup:

src/params/slug.js

export function match(str) {
  return /^[a-z0-9]+(?:-[a-z0-9]+)*$/gim.test(str);
}

As is clear, matchers need not be overly complicated. It simply needs to export the match() function, which accepts a string parameter. This matcher then tests that string against a regex literal, returning true for a match and false for a mismatch. This regex tests for one or more string or number characters followed by a character, which must be followed by one or more string or number characters. A string ending with a character is considered invalid.

Applying matchers

When applying a matcher to a specific route, the value following the = character is the name of the given matcher. Another example may include creating a matcher that tests for integers. That rule could be enforced on a dynamic route by setting the parameter like so: [param=integer] where params/integer.js is the name of the matcher file.

To apply the matcher we just created to our news articles, we need to rename src/routes/news/[slug] to src/routes/news/[slug=slug]. Once we have adjusted the parameter in our route accordingly, we can go back and view our news articles just as we did before. Of course, the existing articles will match just fine as they contain valid slugs. To test that this matcher is being applied before we run our database lookup, we can create a new article in src/lib/articles.json. The content and title of the new article object are irrelevant but by creating an article with an invalid slug, we can confirm the matcher is working. Once you have created an article with a bad slug, attempt to view it. You should receive a 404 Not Found error even though the article exists. This is because the dynamic parameter passed in did not match our provided regular expression.

While regular expressions can be intimidating to work with, it’s comforting to know that SvelteKit empowers developers to harness the power behind them. Being able to do so ensures our applications work as we intend them to. However, there may still be instances where SvelteKit routes to an endpoint in an unexpected way. To avoid these situations, let’s take a look at which routing rules take precedence over others.

Sorting

Since it is entirely possible for a URL to match multiple routes, it’s important to understand which routing rules will be executed in which order. Similar to how CSS rules are given different weights, so too are the rules in SvelteKit’s routing. So which routing rules will be executed when and how do we avoid collisions?

  1. More specific routes will always take precedence over less specific routes. A route without a parameter is considered the highest level. For example, src/routes/about/+page.svelte will be executed before src/routes/[param]/+page.svelte.
  2. Applying a matcher to a dynamic parameter will give it a higher priority than those without a matcher. Calling src/routes/news/[slug=slug]/+page.svelte will be given priority over src/routes/news/[slug]/+page.svelte.
  3. Optional and rest parameters are given the least preference. If they are not the final section of the route, they are ignored. For instance, src/routes/[x]/+page.svelte will execute before src/routes/[...rest]/+page.svelte.
  4. Tiebreakers are determined by alphabetical order of the parameters. That is, src/routes/[x]/+page.svelte will execute before src/routes/[z]/+page.svelte.

If you’re planning to leverage the more advanced routing features of SvelteKit, then understanding these rules is an absolute must. Try customizing the routes created in your project and adjusting them to create collisions. See if you can resolve the collisions yourself or predict which pages will be called before others. Next, we’ll look at how to manage special characters in URLs via encoding.

Encoding

At some point in their career, every developer has encountered issues with encoding, yet no one ever takes the time to fully understand them. Since you’re a busy developer, eager to get started building, and you probably didn’t pick up this book to get lectured about encoding, we’ll keep this short. To prevent serious frustration when building routes that make use of special characters, SvelteKit lets us encode the routes so they may be used in URLs. Of course, some characters such as [ ] ( ) # and % have special meanings either in SvelteKit or the browser and so they are mostly off limits. However, they can still be used when properly encoded in the routing mechanism and URL encoded for the browser.

When creating routes with special characters in SvelteKit, the special characters are written inside of square brackets [ ] similar to how dynamic parameters are. However, they are then prefixed by x+ and followed by the hexadecimal value of the character. An example of this is when creating a route to the /.well-known/ directory, which could be represented like so: src/routes/[x+2e]well-known/+page.svelte. In most cases, there should be no issues with this route and encoding won’t be necessary, but we’re using it for demonstration purposes. Go ahead and create it in your project. In the browser, navigate to the development site and append the /.well-known/ route to confirm it works. Now try to create the route /?-help/. Because ? is a special character in the browser, it must be encoded to properly execute. We can create the route using the hexadecimal values like this: src/routes/[x+3f]-help/+page.svelte. But we won’t be able to access the web page at /?-help/. Instead, we’ll need to access that particular route at /%3f%-help/. Whenever using a special character in routes, consider encoding it with the hexadecimal values beforehand.

To obtain the hexadecimal value of a character, you can use the following JS snippet: ':'.charCodeAt(0).toString(16); where : is the special character you’d like to retrieve the hexadecimal value for. We’re not limited to only using hexadecimal values for simple text either. SvelteKit’s routing also supports Unicode standards. As such, we could use an emoji directly within our routes. If we needed to work around encoding with Unicode, we could use [u+xxxx] where xxxx is the Unicode code point.

To ensure our application behaves as expected, it’s essential to know how to properly encode special characters. We also looked at how we can apply matchers to routes so that we can ensure dynamic parameters are of the type we’re expecting them to be. And with our cursory glance at how routes are given precedence over others, you should feel comfortable exploring even more advanced techniques in your application’s routing.

Advanced layouts

The more complex the application, the more complex the structure becomes. To keep application logic organized we can utilize more advanced routing mechanisms such as layout groups and breakouts. By using layout groups, we can organize various layout components without cluttering the application URL. And by inserting simple syntax into pages and templates, we can break a layout or page out from its hierarchy while keeping the structure of our application intact.

Since we organize our application components into logical groupings, it makes sense to organize application functionality into logical groupings as well. To demonstrate using a real-world example, consider interface components that are available to logged-in users but not available to anonymous users. When logged in, users can interact with other users through comments, change their profile information, or view their own notifications. A user of the site that is not logged in should not see any of these components. With what we’ve learned so far about layouts, creating different layouts for each type of user could potentially run us into the issue of affecting our application’s clean URL. This is where we can harness SvelteKit’s layout groups.

When creating a layout group, use parenthesis ( ) to surround the directory name. All content inside of that layout group will then be included in the group and slotted in the +layout.svelte file found there. To demonstrate layout groups, we’ll create two groups: (app) and (site). Inside of (app), we’ll move logic related to the application features, and inside of (site), we’ll move logic commonly found in basic websites. Our new routes directory structure should look similar to this:

src/routes/

src/
|_routes/
   |_(app)/
   |  |_comment/
   |  |_login/
   |  |_notifications/
   |  |_store/
   |
   |_(site)/
   |  |_about/
   |  |_fetch/
   |  |_news/
   |
   |_api/
   |_+layout.server.js
   |_+layout.svelte
   |_+page.svelte

After shuffling our folders around, we can create a layout for each of our new layout groups:

src/routes/(app)/+layout.svelte

<div class='app_layout'>
  <slot />
</div>
<style>
  .app_layout {
    background: #cac4c4;
    padding: 1rem;
  }
</style>

In this layout file, we’re wrapping all of the content that will be rendered in the Svelte <slot /> directive with another <div> element that will apply a background color. For simplicity’s sake, we’re only attempting to demonstrate how different layout groups work. The next file does exactly the same thing but applies a different color:

src/routes/(site)/+layout.svelte

<div class='site_layout'>
  <slot />
</div>
<style>
  .site_layout {
    background: #83a8ee;
    padding: 1rem;
  }
</style>

After saving these layouts, you’ll notice the application shows different background colors when navigating the browser to comment/, login/, notifications/, and store/ than it does for about/, fetch/, and news/. However, our URLs remain exactly the same!

For cases where want to break a particular layout or page out of the existing hierarchy, we can append the @ character to the filename. For example, +page@ or +layout@. We can then follow it up with the name of the directory we would like it to inherit directly from. If no name is provided after the @ character, then the root layout will be utilized. We can see this in action by renaming src/routes/(app)/store/products/[...details]/+page.svelte to src/routes/(app)/store/products/[...details]/+page@(app).svelte. Doing so moves the product page out of the product and store layouts. Try renaming it to [email protected] to keep the store layout or [email protected] to take it all the way back to the root layout. Of course, our product links are no longer visible as the markup to show them was included in src/routes/(app)/store/products/+layout.svelte, but we’re only trying to demonstrate how you can break a page out of its immediate layouts. This functionality can be useful for separating your application logic into administrative or authenticated sections while keeping the URL unaffected.

We’ve just seen how we can break out of layouts using @ symbols in the Svelte component naming conventions. When we include @ followed by the name of our desired layout, the file will inherit directly from the named layout instead of all layouts between. We’ve also seen how we can create layout groups to keep our project structured without disrupting the application URL. With everything we’ve covered, you should be capable of meeting even the most complex routing requirements for any SvelteKit project.

Summary

In the chapters leading up to this, we covered core routing concepts. In this chapter, we looked at the more advanced techniques available in SvelteKit. These techniques can help us further customize our application and address edge cases. When it comes to routing, we now have an understanding of how we can create optional parameters with default values. We’ve also seen how rest parameters can be used to create a shareable URL of unknown lengths. Matching was shown to be useful for ensuring our application is receiving parameters of the expected types. We also saw how SvelteKit prioritizes certain routing rules over others, which is helpful for understanding the order of execution when a URL matches multiple routes. After covering how to encode special characters in routes, we looked at how we can create layout groups and even break out of the layout hierarchies while keeping application logic intact. If you’ve finished this chapter and feel comfortable with everything learned, you’ll be able to handle even the strangest of edge cases encountered while building the routing of your SvelteKit application.

In the next chapter, we’ll take a short break from routing to analyze various SvelteKit adapters and the environments they are used in. We’ll also take a closer look at page options and attempt to build our application for a production environment for the first time.

Resources

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.19.30.232