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

example if you need a custom 404 page for pages inside /categories/
Pasted image 20230815193100.png

make <name>.jsfiles 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].

layouts

the tag in +layout.svelte files render whatever is in +page.svelte. layouts are nestable

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 fromand 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.

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 optional action 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.

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'));
	}
};

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.

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()
Pasted image 20230815231129.png

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.