learning sveltekit
svelte is a UI component framework (compiler).
sveltekit is a web app framework that takes care of things like routing, SSR, serving pages,
routing
-
folder names are URL routes
-
+page.svelte is the file where the contents of the route are. so for ex: routes/+page.svelte are the contents of the
/
route. -
routes with dynamic parameters use [] around a valid variable name. for ex:
src/routes/blog/[var]/+page.svelte
will create a route that matches/blog/one
and/blog/two
.
Multiple route parameters can appear within one URL segment, as long as they are separated by at least one static character:foo/[bar]x[baz]
is a valid route where[bar]
and[baz]
are dynamic parameters -
optional dynamic parameters. specified with double brackets route.
[[]]
example: like a pathname to determine a locale (/fr/..., /de/...)
since it is an optional dynamic parameter, watch out for matching routes that would clash names. -
rest parameter.
to match an unknown number of path segments use[...rest]
.
useful as 'catch-all' routes.
example if you need a custom 404 page for pages inside /categories/
- param matchers
to prevent the router from matching on invalid input.
matchers run on both server and browser.
make <name>.js
files inside a src/params/
directory that export a match
function.
export function match(value) {
return /^[0-9a-f]{6}$/.test(value);
} // is a regex object and .test value tests the expresssion against a string, returning a bool.
to use the matcher rename src/routes/[param]
to src/routes/[param=hex]
.
-
route groups
for using layouts without affecting the route.
example: you want /app and /account behind authentication but want /about to be public.
create (authed) directory with layout files inside of it. > rename routes I want to be in the group to(authed)/<route>
-
breaking out of layouts
by adding@
followed by the name of a parent to 'reset' to.
+page@.svelte
resets to root.+page@a.svelte
resets to the parent layout nameda
.
layouts
the
shared modules
Because SvelteKit uses directory-based routing, it's easy to place modules and components alongside the routes that use them. A good rule of thumb is 'put code close to where it's used'.
Sometimes, code is used in multiple places. When this happens, it's useful to have a place to put them that can be accessed by all routes without needing to prefix imports with ../../../../
. In SvelteKit, that place is the src/lib
directory. Anything inside this directory can be accessed by any module in src
via the $lib
alias.
<script> import { message } from '$lib/message.js'; </script>
API routes
you can create API routes by adding a +server.js
file that exports functions corresponding to HTTP methods. "GET", "POST", PUT, PATCH and DELETE.
these files can be placed inside the directory structure to correspond to a specific URL route.
stores
sveltekit makes 3 readonly stores available via the $app/stores module: page, navigating and updates.
the page
module contains current page info like: url, params, route, status, error, data, form.
Like any other store you can reference it with $
. for example: $page.url.pathname
the navigating
store represents the current navigation. it contains the properties from
and to
, which contain params, route, url
properties, and type
which is the type of navigation (eg, link, popstate or goto)
the updated
store contains true
or false
depending on whether a new version of the app has been deployed since the page was first opened. for this to work, the svelte.config.js
file must specify kit.version.pollInterval
. version changes only happen in production, not durign development. $updated will always be false on development.
you can manually check for new versions, regardless of pollINterval
by calling updated.check()
errors
there are 2 types of error: expected and unexpected.
expected is created with the error
helper from @sveltejs/kit
.
ex:
import {error} from 'sveltejs/kit';`
throw error(420, 'Enhance your calm');
any other error is treated as unexpected. like a throw new Error('oopsie');
by throwing expected errors you are telling sveltekit that you are known what you are doing. unexpected errors are assumed to be bugs in your app. unexpected errors message and stack trace will be logged to the console.
expected error message is shown to the user, unexpected is redacted and replaced with generic 'Internal Error' and 500 status code. bc error msgs contain sensitive data.
when something goes wrong inside a load
function, sveltekit renders an error page. default page is bland, to customize create a +error.svelte
file/component. this files can be created more granularly inside routes as well.
-
customize fallback error page by creating a
src/error.html
file. the file can include -%sveltekit.status%
 — the HTTP status code and%sveltekit.error.message%
 — the error message. -
you throw redirects from one page to another.
throw redirect(307, '/b');
loading
regular loading
Every page of your app can declare a load
function in a +page.server.js
file alongside the +page.svelte
file. As the file name suggests, this module only ever runs on the server, including for client-side navigations.
we then access the loaded data on the +page.svelte file via the data prop: <script> export let data;</script>
> <h1>data.title</h1>
layout loading
Just as +layout.svelte
files create UI for every child route, +layout.server.js
files load data for every child route.
headers and cookies
on the loading function you can also use a setHeaders function. to change the headers of the response. Most commonly this is used to change caching behaviour w/ the Cache-Control
response header
export function load({ setHeaders }) {
setHeaders({
'Content-Type': 'text/plain'
});
}
however, this setHeaders
function can't be used to set cookies with the Set-Cookie
header. Instead, you should use the cookies
API. you can read cookies with cookies.get(name, options);
. to set a cookie use cookies.set(name, value, options);
. it is strongly reccommended to explicitly configure the path when using a cookie, since browser's default behaviour is to set the cookie on the parent of the current path.
forms
use the
element to send data to the server.named forms
the
element has an optionalaction
attribute, which is similar to an <a>
element's href
attrbute.<form method="POST" action="?/create"></form>
(I thought about this on the way on my own!)
pages that only have one action are rare.
note: default actions cannot coexist with named actions.
example of a delete button button form on a todo app:
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete" />
note the hidden input with an id identifier. I believe I have done things like this in the past.
form validation
sveltekit has built in form validation.
- can mark an input as required
- can validate inputs server side (can be a directory called 'server' that has all server related functions like interacting with a database)
- can throw errors, or return a
fail
function call - can access returned values via the
form
prop, which is only ever populated after a form submission. similar to howexport let data;
works:export let form;
- can also return data from an action without wrapping it in
fail
, for example to show a 'success!' msg when data was saved - and it will be available via theform
prop.
server side actions
on the +page.server.js
file, apart from the load function, I can define actions like so:
export const actions = {
default: async ({ cookies, request }) => {
const data = await request.formData();
db.createTodo(cookies.get('userid'), data.get('description'));
}
};
- request is a standard Request object;
- await request.formData() returns a FormData instance
progressive enhancement on forms
since we are usings <form>
, apps work even if the user doesn't have javascript. in cases when users have js, we can progressively enhance the experience, the same way SvelteKit progressively enhance <a>
elements by using client-side routing.
import { enhance } from '$app/forms';
- add the
use:enhance
directive to the<form>
elements - that is it, now a js enhanced form will emulate how the native browser behaves.
- we can go further than this, by provinding a callback, we can add things like pending states and optimistic UI.
universal load functions
done by exporting load
function from +page.js
and +layout.js
files
when to use: loading data from an external API, want to use in-memory data, want to delay navitaion until an image has been preloaded, need to return something from load
that can't be serialized (like components).
to turn server load
functions into universal load
funcitons, rename each +page.server.js
file to +page.js
.then, the functions will run on the server during server-side rendering, but will also run in the broweser when the app hydrates or the user performs a client-side navigation.
occassionlly, you might need to use a server load function and a universal load function together. like you might need to return data from the server, but also return a value that can't be serialized as server data.
we can access server data in +page.js via the data
property.
using parent data
Occasionally it's useful for the load functions themselves to access data from their parents. This can be done with await parent()
invalidation
sveltekit calls your load
functions, but only if it thinks something has changed.
you can manually invalidate a url to make it rerun the load function using the invalidate(...)
function. you can pass a url or a function(in case you want to invalidate based on a pattern) as a parameter.
invalidateAll()
custom dependencies
(useful for invalidations)
calling fetch(url) inside a load function registers url
as a dependency. sometimes it's not appropriate to use fetch
, in which case you can specify a dependency manually with the depends(url)
function. we can create custom invalidation keys like data:now
.
now when invalidating a url, you can call invalidate('data:now');
instead of invalidate('/api/now');
rendering
pages can be prerendered, rendered server side or single page app.
page options
ssr
export const ssr = false;
disables server side rendering for that page/layout (group of pages).
setting ssr to false inside a root +layout.server.js
effectively turns your entire app into a single-page app (SPA).
CSR
client-side rendering is what makes a page interactive. it enables svelteKit to update a page upon navigation without a full-page reload.
you can disable csr with export const csr = false;
. this means that no JavaScript is served to the client.
prerendering
prerendering means generating HTML for a page once, at build time, rather than dynamically for each request.
serving static data is extremely cheap and performant.
tradeoff is that the build process takes longer, and prerendered content can only be updated by building and deploying a new version of the application.
export const prerender = true;
to enable prerender.
basic rules:
two users hitting it directly must get the same content from the server.
the page must not have form actions.
pages with dynamic route parameters can be prerendered as long as they are specified in the prerender.entries
configuration or can be reached by following links from pages that are in that file.
Setting prerender
to true
inside your root +layout.server.js
effectively turns SvelteKit into a static site generator (SSG).
trailing slashes
sveltekit strips trailing slashes. requests for /foo/
will redirect to /foo
to ensure slash is always present: export const trailingSlash = 'always';
, 'never' to ensure its never present and 'ignore' for both cases.
preloading
when an element has data-sveltekit-preload-data
attribute, SvelteKit will begin the navigation as soon as the user hovers over the link. you can specify values 'hover', 'tap' and 'off'.
sometimes preload-data
loads data anticipating a navigation that doesn't happen, which might be undesirable. an alternative is data-sveltekit-preload-code
which preloads the JavaScript needed by a given route without eagerly loading it's data. This attribute can have the following value: "eager" (preload everything on the page following a navigation), "viewport"(preload everything as it appears in the viewport), "hover", "tap", "off".
you can also import progranmatically:
import { preloadCode, preloadData } from '$app/navigation';
preloadData('/foo');
preloadCode('/bar');
reloading
SvelteKit will navigate between pages without refreshing the page. in some cases you might want to disable this behaviour by adding the data-sveltekit-reload
attribute to an individual link or element that contains links (like a nav).
env vars
can be added to a .env
file and will be made available to the app. can alsu use .env.local
or .env.[mode]
files (check vite docs). make sure to add files with sensitive data to .gitignore
.
import the env vars like this: import { PASSPHRASE } from '$env/static/private';
the static part refers to these values being known at build time, and can be statically replaced.
for dynamic vars, (not known until the app is ran)
import {env} from '$env/dynamic/private';
when env vars can be safely exposed. do this by prefixing the name with PUBLIC_
. just like private env vars, it is preferable to use statis values, but if necessary we can use dynamic values instead. like so: import {env} from '$env/dynamic/public';
and reference: env.PUBLIC_MYVAR
.
it is important to not send sensitive data to the browser. when importing an env var to a +page.svelte
file an error overlay pops up. these vars can only be imported into server module: +page.server.js
, +layout.server.js
, +server.js
, any module ending in .server.js
and any module inside src/lib/server
.