This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.
1. Introduction
The simple "Inventory Maintenance" webapp created in the last section of this series has a silly design - a single address displays a list of current products and adds a button that launches a popup to add new ones. This is both confusing and restrictive. In the real world, a system like this would need to serve two entirely different groups of users - readers on the one hand and maintainers on the other. It would be much better if each of these two bits of functionality had its own address:
- products display page: http://localhost:5173/products-display
- products maintenance page: http://localhost:5173/products-maintenance
In the webapp design world, addresses like these are referred to as routes (they represent the "route" which users follow to reach content). The content displayed by following a route is referred to as a page
Webapp users love routes. Once each functional component of webapp acquires an independent existence it becomes much easier to visualise the structure and purpose of the information system. These dovetail nicely with browser "breadcrumb" arrangements that enable you to navigate up and down the branches of the webapp's hierarchy. Also, if a page is frequently accessed, users can save its route as a bookmark to return to it easily by clicking the saved link. Designers like this arrangement too because it creates a clear structure wherein, for example, they can deliver security arrangements that leave some application areas publicly available whereas others are protected by login.
Svelte fixes this problem by baking the webapp route structure into the webapp project's file structure.
Other structural arrangements should also be considered. Users find it helpful if families of related pages employ common elements such as a standard menu bar and page trailer. This gives the page family a consistent appearance and makes it easier for users to navigate the webapp. Developers have struggled in the past to keep these common elements in agreement.
Svelte fixes this problem by arranging a mechanism to source common elements from central locations. In Svelte, these are called "layouts".
Finally, developers struggling to maintain consistency and clarity in the codebase for a large project will feel happier if bits of it that need to operate identically are forced to do so because they use a shared component. Svelte is from top to bottom, a component-based architecture.
So, this section is all about Routes, Layouts and Components.
2. Routing in Svelte
SvelteKit uses your project's filing structure to define the required page structure. If, for example, you want a mywebapp/products-display
page, you need a project folder called src/routes/products-display
with a +page.svelte
file inside it. Once this arrangement is in place, your webapp delivers a separate physical page for each route.
Here's a sample source folder structure for a SvelteKit project. To make things a little more realistic here I've added a "product-details" sub-folder to the "products-display" folder that will enable users to select more information on a product listed by the "products-display" route:
Hang on though. Isn't this going to be a bit confusing? Lots of files all called +page.svelte? Yes, it is, at least at first sight. But it's not so difficult to discipline yourself to check for the owner of a +page.svelte
before you dive in and start editing. And once you've developed a SvelteKit project or two you'll begin to appreciate the value of the convention in declaring the purpose of the arrangement (as you'll see in a moment there are quite a few variations)
Try this out in your current svelte_test
project by creating new products-display
and products-display/product-details
folders containing the following products-display/+page.svelte
and products-display/product-details/+page.svelte
code.
// products-display/+page.svelte - remove before running
<div style="text-align: center;">
<a href="/products-display/product-details">View Product Details for Product 1</a>
</div>
// products-display/product-details/+page.svelte - remove before running
<div style="text-align: center;">
<span>Here are the Product Details for Product 1</span>
</div>
Now run your dev server, start the webapp and change the root address to localhost:5173/productsproducts-display
(making allowance for any variation in port number, of course). It should look like this:
Now, what happens when you click the link? Unsurprisingly, the display changes to this:
Note that the page address has also changed. The link has transferred you to a new address - localhost:5173/products-display/product-details
. Try using the browser's navigation buttons to return to the original page. Yes, everything works exactly as you would expect. Each of these +page.svelte
files has created a navigation route based on its position in the development project hierarchy. You can also bookmark these. All you've had to do to achieve this is to submit yourself to Svelte's file naming conventions. I'd say that's a price well worth paying!
In practice, the "Products display -> Product details" arrangement needs to be dynamic. The "products-display" page would typically list all current products and the <a>
links would carry information to enable it to identify which particular product you were interested in. I'll describe how Svelte would handle this when I cover "components" later in this post.
3. Layouts in Svelte
In the real world, a company's products page would likely provide an elaborate showcase with some sort of header banner featuring the company logo and contact details. Customers would probably find it useful to see the same information on a "product-details" page too.
Although it would be perfectly possible to duplicate these details on the two pages, this would be very undesirable from a maintenance point of view.
Svelte's "layout" facility provides a neat solution to the problem.
Create a +layout.svelte
file in the products-display
folder containing the following code:
// products-display/+layout.svelte - remove before running
<header>
<h3 style="display:flex; justify-content:space-between">
<a href="/about">About</a>
<span>Magical Products Company</span>
<a href="/inventory_search">Search</a>
</h3>
</header>
<slot></slot>
<trailer>
<div style="text-align: center; margin: 3rem; font-weight: bold; ">
<span>© 2024 Magical Products Company</span>
</div>
</trailer>
Now check out the products-display
and products-display/product-details
pages in your browser. Magic! You'll find that the header and trailer layouts have now been wrapped automatically around both pages. The localhost:5173/products-display
page, for example, should now look like:
The <slot></slot>
element within the +layout.svelte
file defines where the content of subject pages will be inserted. See +layout in Svelte docs for more information about the arrangement. When a layout is positioned at the top of a tree of pages, it is applied to all subordinate pages.
Svelte layouts are especially handy for building a "nav" (navigation) bar. This is the array of links or buttons used in many applications to provide a standard, centralized menu for accessing site pages. It is usually positioned at the head of the display, but you could just as easily style the nav bar to appear at the side.
4. Components in Svelte
While the standard "header/footer" layout arrangement is useful, there are many other situations where you might want to introduce a standard piece of HTML into the body of a page. Repeating yourself is never a good idea in coding so the "DRY" (don't repeat yourself) principle applies just as much to Javascript as to HTML.
A Svelte component is a self-contained unit of code that combines HTML, CSS, and JavaScript to create a piece of the user interface (UI). Typically you would store this in a src/lib/MyComponent.svelte
file. Here "MyComponent" would be some appropriate meaningful expression of the component's purpose.
Just as with a Javascript function, a Svelte component will usually need to be provided with some parameters to achieve its purpose, so a UserProfile.svelte
might be referenced in the HTML body section of a +page.svelte
file with a statement like:
<UserProfile userId={userId} />
Here, the userId
parameter will have been declared by the +page.svelte
file with a let userId
statement and seeded with a value. The component will also have been imported into +page.svelte
, so its <script>
section will look like this:
<script>
import { UserProfile} from "$lib/UserProfile.svelte";
let userId;
<script/>
Note the "$" shortcut used in the import declaration. Svelte works out the actual route automatically, saving you from having to work out all the conventional "./" and "//" relative location designators.
Meanwhile, in the UserProfile.svelte
file, the parameter will have been declared as a prop (ie a parameter) with an export statement
<script>
export userId;
<script/>
The UserProfile.svelte
file is now free to reference the userId parameter both in its <script>
and template sections
You'll recall this is exactly the same way a +page.svelte
file gets its data from an associated +page.server.js
file. This is because all +page.svelte
files are themselves components.
And now that you know this, you'll maybe guess that component structures open a way to write products-display
pages that link programmatically to product-details
pages.
First, SvelteKit replaces the src/routes/products-display/product-detail
route with a new, dynamic "parameterised" src/routes/products-display/[productNumber]/
route. This enables it to reference individual +page.svelte
product-details
pages from "anchor" links such as <a href="/products-display/1">View Product 1</a>
. When the SvelteKit router is asked to parse this address it passes it to the following modified version of the original product-details
+page.svelte
file:
// src/routes/products-display/[productNumber]/+page.svelte - remove before running
<script>
export let data;
</script>
<div style="text-align: center;">
<span>Here's the Product Details for Product { data.props.productNumber}</span>
</div>
As you'll see, this is looking for a load() function to supply the productNumber
parameter. You've seen this before (see Post 2.3, so you'll know that this needs to be supplied by a +page.server.js
file. Here it is:
// src/routes/products-display/[productNumber]/+page.server.js - remove before running
export async function load(event) {
const productNumber = event.params.productNumber;
return {
props: {
productNumber
}
};
}
This digs the productNumber
parameter out of the SvelteKit event
object (the complex of properties and methods that follows a transaction throughout its lifecycle) and returns it to the +page.svelte
file.
That was fun! Just to tie things up neatly, here is a version of products-display/+page.svelte
that lists all products in the collection as anchor tags that link them dynamically to product-details pages:
// src/routes/products-display/+page.svelte - remove before running
<script>
export let data;
</script>
<div style="text-align: center">
<h3>Current Product Numbers</h3>
{#each data.products as product}
<!-- display each anchor on a separate line-->
<div style = "margin-top: .35rem;">
<a href="/products-display/{product.productNumber}"
>View Detail for Product {product.productNumber}</a
>
</div>
{/each}
</div>
I won't go into details for the remaining files. The src/routes/products-display/+page.server.js
file that provides the load()
function for the above is perfectly standard. But the load()
function for the dynamic route's src/routes/products-display/[productNumber]/+page.svelte
file now has to use the productNumber
value from the event.params
object to get the associated productDetails
field from the database. This is messy and I don't think it would be helpful to look at that now. But the code is published at Github(???) if you feel you need to see it.
5. Roundup
The Svelte file types described above deliver a "default" design in which pages are primarily initially rendered on the server and initially displayed to the client as pure HTML. Here, in a process Svelte calls "hydration", javascript is added to make the page interactive. Finally, "state" data used on the server to generate the initial HTML is replayed to ensure that everything is still consistent. Overall this arrangement:
- ensures that sensitive application logic, vulnerable form validation logic and valuable security keys are hidden
- makes the most of the (generally) superior processing capability of the server
- encourages better "indexing" by search engines. While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably.
That said, there will always be cases where other arrangements are to be preferred and Svelte is very helpful in providing ways of enabling these.
For example, if the data accessed by a +page.server.js isn't changing very much (ideally, not at all) you can instrument Svelte to pre-render your +page.svelte at "build" time (the point at which the webapp is prepared for "deployment" to the liver server - a subject to be covered later in this series).
Additionally, there may be cases when client-side processing would be a more practical way of loading the data for a +page.svelte
file. Perhaps it doesn’t matter if the data fetch happens asynchronously after the initial page load or if the data can be fetched via APIs that don’t require server-side security. In this case, Svelte offers a +page.js
file to export a load
function. Unlike a +page.server.js
file, a +page.js
can be inspected in the browser
For more information about this and other specialist arrangements, please refer to the SvelteKit Page Options docs
Top comments (0)