diff --git a/apps/svelte.dev/content/docs/kit/v01/10-getting-started/10-introduction.md b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/10-introduction.md new file mode 100644 index 0000000000..189fcb3bcc --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/10-introduction.md @@ -0,0 +1,27 @@ +--- +title: Introduction +--- + +## Before we begin + +> If you're new to Svelte or SvelteKit we recommend checking out the [interactive tutorial](https://learn.svelte.dev). +> +> If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat). + +## What is SvelteKit? + +SvelteKit is a framework for rapidly developing robust, performant web applications using [Svelte](https://svelte.dev/). If you're coming from React, SvelteKit is similar to Next. If you're coming from Vue, SvelteKit is similar to Nuxt. + +To learn more about the kinds of applications you can build with SvelteKit, see the [FAQ](/docs/faq#what-can-i-make-with-sveltekit). + +## What is Svelte? + +In short, Svelte is a way of writing user interface components — like a navigation bar, comment section, or contact form — that users see and interact with in their browsers. The Svelte compiler converts your components to JavaScript that can be run to render the HTML for the page and to CSS that styles the page. You don't need to know Svelte to understand the rest of this guide, but it will help. If you'd like to learn more, check out [the Svelte tutorial](https://svelte.dev/tutorial). + +## SvelteKit vs Svelte + +Svelte renders UI components. You can compose these components and render an entire page with just Svelte, but you need more than just Svelte to write an entire app. + +SvelteKit helps you build web apps while following modern best practices and providing solutions to common development challenges. It offers everything from basic functionalities — like a [router](glossary#routing) that updates your UI when a link is clicked — to more advanced capabilities. Its extensive list of features includes [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) to load only the minimal required code; [offline support](service-workers); [preloading](link-options#data-sveltekit-preload-data) pages before user navigation; [configurable rendering](page-options) to handle different parts of your app on the server via [SSR](glossary#ssr), in the browser through [client-side rendering](glossary#csr), or at build-time with [prerendering](glossary#prerendering); and much more. Building an app with all the modern best practices is fiendishly complicated, but SvelteKit does all the boring stuff for you so that you can get on with the creative part. + +It reflects changes to your code in the browser instantly to provide a lightning-fast and feature-rich development experience by leveraging [Vite](https://vitejs.dev/) with a [Svelte plugin](https://github.com/sveltejs/vite-plugin-svelte) to do [Hot Module Replacement (HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot). diff --git a/apps/svelte.dev/content/docs/kit/v01/10-getting-started/20-creating-a-project.md b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/20-creating-a-project.md new file mode 100644 index 0000000000..a65814e9a5 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/20-creating-a-project.md @@ -0,0 +1,25 @@ +--- +title: Creating a project +--- + +The easiest way to start building a SvelteKit app is to run `npm create`: + +```bash +npm create svelte@latest my-app +cd my-app +npm install +npm run dev +``` + +The first command will scaffold a new project in the `my-app` directory asking you if you'd like to set up some basic tooling such as TypeScript. See [integrations](./integrations) for pointers on setting up additional tooling. The subsequent commands will then install its dependencies and start a server on [localhost:5173](http://localhost:5173). + +There are two basic concepts: + +- Each page of your app is a [Svelte](https://svelte.dev) component +- You create pages by adding files to the `src/routes` directory of your project. These will be server-rendered so that a user's first visit to your app is as fast as possible, then a client-side app takes over + +Try editing the files to get a feel for how everything works. + +## Editor setup + +We recommend using [Visual Studio Code (aka VS Code)](https://code.visualstudio.com/download) with [the Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), but [support also exists for numerous other editors](https://sveltesociety.dev/tools#editor-support). diff --git a/apps/svelte.dev/content/docs/kit/v01/10-getting-started/30-project-structure.md b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/30-project-structure.md new file mode 100644 index 0000000000..e9a63473b0 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/10-getting-started/30-project-structure.md @@ -0,0 +1,92 @@ +--- +title: Project structure +--- + +A typical SvelteKit project looks like this: + +```bash +my-project/ +├ src/ +│ ├ lib/ +│ │ ├ server/ +│ │ │ └ [your server-only lib files] +│ │ └ [your lib files] +│ ├ params/ +│ │ └ [your param matchers] +│ ├ routes/ +│ │ └ [your routes] +│ ├ app.html +│ ├ error.html +│ ├ hooks.client.js +│ ├ hooks.server.js +│ └ service-worker.js +├ static/ +│ └ [your static assets] +├ tests/ +│ └ [your tests] +├ package.json +├ svelte.config.js +├ tsconfig.json +└ vite.config.js +``` + +You'll also find common files like `.gitignore` and `.npmrc` (and `.prettierrc` and `.eslintrc.cjs` and so on, if you chose those options when running `npm create svelte@latest`). + +## Project files + +### src + +The `src` directory contains the meat of your project. Everything except `src/routes` and `src/app.html` is optional. + +- `lib` contains your library code (utilities and components), which can be imported via the [`$lib`](modules#$lib) alias, or packaged up for distribution using [`svelte-package`](packaging) + - `server` contains your server-only library code. It can be imported by using the [`$lib/server`](server-only-modules) alias. SvelteKit will prevent you from importing these in client code. +- `params` contains any [param matchers](advanced-routing#matching) your app needs +- `routes` contains the [routes](routing) of your application. You can also colocate other components that are only used within a single route here +- `app.html` is your page template — an HTML document containing the following placeholders: + - `%sveltekit.head%` — `` and ` + +

{data.title}

+
{@html data.content}
+``` + +> Note that SvelteKit uses `` elements to navigate between routes, rather than a framework-specific `` component. + +### +page.js + +Often, a page will need to load some data before it can be rendered. For this, we add a `+page.js` module that exports a `load` function: + +```js +/// file: src/routes/blog/[slug]/+page.js +import { error } from '@sveltejs/kit'; + +/** @type {import('./$types').PageLoad} */ +export function load({ params }) { + if (params.slug === 'hello-world') { + return { + title: 'Hello world!', + content: 'Welcome to our blog. Lorem ipsum dolor sit amet...' + }; + } + + throw error(404, 'Not found'); +} +``` + +This function runs alongside `+page.svelte`, which means it runs on the server during server-side rendering and in the browser during client-side navigation. See [`load`](load) for full details of the API. + +As well as `load`, `+page.js` can export values that configure the page's behaviour: + +- `export const prerender = true` or `false` or `'auto'` +- `export const ssr = true` or `false` +- `export const csr = true` or `false` + +You can find more information about these in [page options](page-options). + +### +page.server.js + +If your `load` function can only run on the server — for example, if it needs to fetch data from a database or you need to access private [environment variables](modules#$env-static-private) like API keys — then you can rename `+page.js` to `+page.server.js` and change the `PageLoad` type to `PageServerLoad`. + +```js +/// file: src/routes/blog/[slug]/+page.server.js + +// @filename: ambient.d.ts +declare global { + const getPostFromDatabase: (slug: string) => { + title: string; + content: string; + } +} + +export {}; + +// @filename: index.js +// ---cut--- +import { error } from '@sveltejs/kit'; + +/** @type {import('./$types').PageServerLoad} */ +export async function load({ params }) { + const post = await getPostFromDatabase(params.slug); + + if (post) { + return post; + } + + throw error(404, 'Not found'); +} +``` + +During client-side navigation, SvelteKit will load this data from the server, which means that the returned value must be serializable using [devalue](https://github.com/rich-harris/devalue). See [`load`](load) for full details of the API. + +Like `+page.js`, `+page.server.js` can export [page options](page-options) — `prerender`, `ssr` and `csr`. + +A `+page.server.js` file can also export _actions_. If `load` lets you read data from the server, `actions` let you write data _to_ the server using the `
` element. To learn how to use them, see the [form actions](form-actions) section. + +## +error + +If an error occurs during `load`, SvelteKit will render a default error page. You can customise this error page on a per-route basis by adding an `+error.svelte` file: + +```svelte + + + +

{$page.status}: {$page.error.message}

+``` + +SvelteKit will 'walk up the tree' looking for the closest error boundary — if the file above didn't exist it would try `src/routes/blog/+error.svelte` and then `src/routes/+error.svelte` before rendering the default error page. If _that_ fails (or if the error was thrown from the `load` function of the root `+layout`, which sits 'above' the root `+error`), SvelteKit will bail out and render a static fallback error page, which you can customise by creating a `src/error.html` file. + +If the error occurs inside a `load` function in `+layout(.server).js`, the closest error boundary in the tree is an `+error.svelte` file _above_ that layout (not next to it). + +If no route can be found (404), `src/routes/+error.svelte` (or the default error page, if that file does not exist) will be used. + +> `+error.svelte` is _not_ used when an error occurs inside [`handle`](hooks#server-hooks-handle) or a [+server.js](#server) request handler. + +You can read more about error handling [here](errors). + +## +layout + +So far, we've treated pages as entirely standalone components — upon navigation, the existing `+page.svelte` component will be destroyed, and a new one will take its place. + +But in many apps, there are elements that should be visible on _every_ page, such as top-level navigation or a footer. Instead of repeating them in every `+page.svelte`, we can put them in _layouts_. + +### +layout.svelte + +To create a layout that applies to every page, make a file called `src/routes/+layout.svelte`. The default layout (the one that SvelteKit uses if you don't bring your own) looks like this... + +```html + +``` + +...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a `` for the page content. For example, let's add a nav bar: + +```html +/// file: src/routes/+layout.svelte +
+ + +``` + +If we create pages for `/`, `/about` and `/settings`... + +```html +/// file: src/routes/+page.svelte +

Home

+``` + +```html +/// file: src/routes/about/+page.svelte +

About

+``` + +```html +/// file: src/routes/settings/+page.svelte +

Settings

+``` + +...the nav will always be visible, and clicking between the three pages will only result in the `

` being replaced. + +Layouts can be _nested_. Suppose we don't just have a single `/settings` page, but instead have nested pages like `/settings/profile` and `/settings/notifications` with a shared submenu (for a real-life example, see [github.com/settings](https://github.com/settings)). + +We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav): + +```svelte + + + +

Settings

+ + + + +``` + +By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#advanced-layouts) can help you. + +### +layout.js + +Just like `+page.svelte` loading data from `+page.js`, your `+layout.svelte` component can get data from a [`load`](load) function in `+layout.js`. + +```js +/// file: src/routes/settings/+layout.js +/** @type {import('./$types').LayoutLoad} */ +export function load() { + return { + sections: [ + { slug: 'profile', title: 'Profile' }, + { slug: 'notifications', title: 'Notifications' } + ] + }; +} +``` + +If a `+layout.js` exports [page options](page-options) — `prerender`, `ssr` and `csr` — they will be used as defaults for child pages. + +Data returned from a layout's `load` function is also available to all its child pages: + +```svelte + + +``` + +> Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently rerun [`load`](load) functions when necessary. + +### +layout.server.js + +To run your layout's `load` function on the server, move it to `+layout.server.js`, and change the `LayoutLoad` type to `LayoutServerLoad`. + +Like `+layout.js`, `+layout.server.js` can export [page options](page-options) — `prerender`, `ssr` and `csr`. + +## +server + +As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS`, and `HEAD` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. + +For example we could create an `/api/random-number` route with a `GET` handler: + +```js +/// file: src/routes/api/random-number/+server.js +import { error } from '@sveltejs/kit'; + +/** @type {import('./$types').RequestHandler} */ +export function GET({ url }) { + const min = Number(url.searchParams.get('min') ?? '0'); + const max = Number(url.searchParams.get('max') ?? '1'); + + const d = max - min; + + if (isNaN(d) || d < 0) { + throw error(400, 'min and max must be numbers, and min must be less than max'); + } + + const random = min + Math.random() * d; + + return new Response(String(random)); +} +``` + +The first argument to `Response` can be a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), making it possible to stream large amounts of data or create server-sent events (unless deploying to platforms that buffer responses, like AWS Lambda). + +You can use the [`error`](modules#sveltejs-kit-error), [`redirect`](modules#sveltejs-kit-redirect) and [`json`](modules#sveltejs-kit-json) methods from `@sveltejs/kit` for convenience (but you don't have to). + +If an error is thrown (either `throw error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors). + +> When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them. + +### Receiving data + +By exporting `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`/`HEAD` handlers, `+server.js` files can be used to create a complete API: + +```svelte + + + + + + = +{total} + + +``` + +```js +/// file: src/routes/api/add/+server.js +import { json } from '@sveltejs/kit'; + +/** @type {import('./$types').RequestHandler} */ +export async function POST({ request }) { + const { a, b } = await request.json(); + return json(a + b); +} +``` + +> In general, [form actions](form-actions) are a better way to submit data from the browser to the server. + +> If a `GET` handler is exported, a `HEAD` request will return the `content-length` of the `GET` handler's response body. + +### Fallback method handler + +Exporting the `fallback` handler will match any unhandled request methods, including methods like `MOVE` which have no dedicated export from `+server.js`. + +```js +// @errors: 7031 +/// file: src/routes/api/add/+server.js +import { json, text } from '@sveltejs/kit'; + +export async function POST({ request }) { + const { a, b } = await request.json(); + return json(a + b); +} + +// This handler will respond to PUT, PATCH, DELETE, etc. +/** @type {import('./$types').RequestHandler} */ +export async function fallback({ request }) { + return text(`I caught your ${request.method} request!`); +} +``` + +> For `HEAD` requests, the `GET` handler takes precedence over the `fallback` handler. + +### Content negotiation + +`+server.js` files can be placed in the same directory as `+page` files, allowing the same route to be either a page or an API endpoint. To determine which, SvelteKit applies the following rules: + +- `PUT`/`PATCH`/`DELETE`/`OPTIONS` requests are always handled by `+server.js` since they do not apply to pages +- `GET`/`POST`/`HEAD` requests are treated as page requests if the `accept` header prioritises `text/html` (in other words, it's a browser page request), else they are handled by `+server.js`. +- Responses to `GET` requests will include a `Vary: Accept` header, so that proxies and browsers cache HTML and JSON responses separately. + +## $types + +Throughout the examples above, we've been importing types from a `$types.d.ts` file. This is a file SvelteKit creates for you in a hidden directory if you're using TypeScript (or JavaScript with JSDoc type annotations) to give you type safety when working with your root files. + +For example, annotating `export let data` with `PageData` (or `LayoutData`, for a `+layout.svelte` file) tells TypeScript that the type of `data` is whatever was returned from `load`: + +```svelte + + +``` + +In turn, annotating the `load` function with `PageLoad`, `PageServerLoad`, `LayoutLoad` or `LayoutServerLoad` (for `+page.js`, `+page.server.js`, `+layout.js` and `+layout.server.js` respectively) ensures that `params` and the return value are correctly typed. + +If you're using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types _entirely_! Svelte's IDE tooling will insert the correct types for you, so you'll get type checking without writing them yourself. It also works with our command line tool `svelte-check`. + +You can read more about omitting `$types` in our [blog post](https://svelte.dev/blog/zero-config-type-safety) about it. + +## Other files + +Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them. + +If components and modules are needed by multiple routes, it's a good idea to put them in [`$lib`](modules#$lib). + +## Further reading + +- [Tutorial: Routing](https://learn.svelte.dev/tutorial/pages) +- [Tutorial: API routes](https://learn.svelte.dev/tutorial/get-handlers) +- [Docs: Advanced routing](advanced-routing) diff --git a/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/20-load.md b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/20-load.md new file mode 100644 index 0000000000..f867fe6d97 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/20-load.md @@ -0,0 +1,645 @@ +--- +title: Loading data +--- + +Before a [`+page.svelte`](routing#page-page-svelte) component (and its containing [`+layout.svelte`](routing#layout-layout-svelte) components) can be rendered, we often need to get some data. This is done by defining `load` functions. + +## Page data + +A `+page.svelte` file can have a sibling `+page.js` that exports a `load` function, the return value of which is available to the page via the `data` prop: + +```js +/// file: src/routes/blog/[slug]/+page.js +/** @type {import('./$types').PageLoad} */ +export function load({ params }) { + return { + post: { + title: `Title for ${params.slug} goes here`, + content: `Content for ${params.slug} goes here` + } + }; +} +``` + +```svelte + + + +

{data.post.title}

+
{@html data.post.content}
+``` + +Thanks to the generated `$types` module, we get full type safety. + +A `load` function in a `+page.js` file runs both on the server and in the browser (unless combined with `export const ssr = false`, in which case it will [only run in the browser](https://kit.svelte.dev/docs/page-options#ssr)). If your `load` function should _always_ run on the server (because it uses private environment variables, for example, or accesses a database) then it would go in a `+page.server.js` instead. + +A more realistic version of your blog post's `load` function, that only runs on the server and pulls data from a database, might look like this: + +```js +/// file: src/routes/blog/[slug]/+page.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getPost(slug: string): Promise<{ title: string, content: string }> +} + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/database'; + +/** @type {import('./$types').PageServerLoad} */ +export async function load({ params }) { + return { + post: await db.getPost(params.slug) + }; +} +``` + +Notice that the type changed from `PageLoad` to `PageServerLoad`, because server `load` functions can access additional arguments. To understand when to use `+page.js` and when to use `+page.server.js`, see [Universal vs server](load#universal-vs-server). + +## Layout data + +Your `+layout.svelte` files can also load data, via `+layout.js` or `+layout.server.js`. + +```js +/// file: src/routes/blog/[slug]/+layout.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getPostSummaries(): Promise> +} + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/database'; + +/** @type {import('./$types').LayoutServerLoad} */ +export async function load() { + return { + posts: await db.getPostSummaries() + }; +} +``` + +```svelte + + + +
+ + +
+ + +``` + +Data returned from layout `load` functions is available to child `+layout.svelte` components and the `+page.svelte` component as well as the layout that it 'belongs' to. + +```diff +/// file: src/routes/blog/[slug]/+page.svelte + + +

{data.post.title}

+
{@html data.post.content}
+ ++{#if next} ++

Next post: {next.title}

++{/if} +``` + +> If multiple `load` functions return data with the same key, the last one 'wins' — the result of a layout `load` returning `{ a: 1, b: 2 }` and a page `load` returning `{ b: 3, c: 4 }` would be `{ a: 1, b: 3, c: 4 }`. + +## $page.data + +The `+page.svelte` component, and each `+layout.svelte` component above it, has access to its own data plus all the data from its parents. + +In some cases, we might need the opposite — a parent layout might need to access page data or data from a child layout. For example, the root layout might want to access a `title` property returned from a `load` function in `+page.js` or `+page.server.js`. This can be done with `$page.data`: + +```svelte + + + + + {$page.data.title} + +``` + +Type information for `$page.data` is provided by `App.PageData`. + +## Universal vs server + +As we've seen, there are two types of `load` function: + +* `+page.js` and `+layout.js` files export _universal_ `load` functions that run both on the server and in the browser +* `+page.server.js` and `+layout.server.js` files export _server_ `load` functions that only run server-side + +Conceptually, they're the same thing, but there are some important differences to be aware of. + +### When does which load function run? + +Server `load` functions _always_ run on the server. + +By default, universal `load` functions run on the server during SSR when the user first visits your page. They will then run again during hydration, reusing any responses from [fetch requests](#making-fetch-requests). All subsequent invocations of universal `load` functions happen in the browser. You can customize the behavior through [page options](page-options). If you disable [server side rendering](page-options#ssr), you'll get an SPA and universal `load` functions _always_ run on the client. + +A `load` function is invoked at runtime, unless you [prerender](page-options#prerender) the page — in that case, it's invoked at build time. + +### Input + +Both universal and server `load` functions have access to properties describing the request (`params`, `route` and `url`) and various functions (`fetch`, `setHeaders`, `parent` and `depends`). These are described in the following sections. + +Server `load` functions are called with a `ServerLoadEvent`, which inherits `clientAddress`, `cookies`, `locals`, `platform` and `request` from `RequestEvent`. + +Universal `load` functions are called with a `LoadEvent`, which has a `data` property. If you have `load` functions in both `+page.js` and `+page.server.js` (or `+layout.js` and `+layout.server.js`), the return value of the server `load` function is the `data` property of the universal `load` function's argument. + +### Output + +A universal `load` function can return an object containing any values, including things like custom classes and component constructors. + +A server `load` function must return data that can be serialized with [devalue](https://github.com/rich-harris/devalue) — anything that can be represented as JSON plus things like `BigInt`, `Date`, `Map`, `Set` and `RegExp`, or repeated/cyclical references — so that it can be transported over the network. Your data can include [promises](#streaming-with-promises), in which case it will be streamed to browsers. + +### When to use which + +Server `load` functions are convenient when you need to access data directly from a database or filesystem, or need to use private environment variables. + +Universal `load` functions are useful when you need to `fetch` data from an external API and don't need private credentials, since SvelteKit can get the data directly from the API rather than going via your server. They are also useful when you need to return something that can't be serialized, such as a Svelte component constructor. + +In rare cases, you might need to use both together — for example, you might need to return an instance of a custom class that was initialised with data from your server. + +## Using URL data + +Often the `load` function depends on the URL in one way or another. For this, the `load` function provides you with `url`, `route` and `params`. + +### url + +An instance of [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), containing properties like the `origin`, `hostname`, `pathname` and `searchParams` (which contains the parsed query string as a [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object). `url.hash` cannot be accessed during `load`, since it is unavailable on the server. + +> In some environments this is derived from request headers during server-side rendering. If you're using [adapter-node](adapter-node), for example, you may need to configure the adapter in order for the URL to be correct. + +### route + +Contains the name of the current route directory, relative to `src/routes`: + +```js +/// file: src/routes/a/[b]/[...c]/+page.js +/** @type {import('./$types').PageLoad} */ +export function load({ route }) { + console.log(route.id); // '/a/[b]/[...c]' +} +``` + +### params + +`params` is derived from `url.pathname` and `route.id`. + +Given a `route.id` of `/a/[b]/[...c]` and a `url.pathname` of `/a/x/y/z`, the `params` object would look like this: + +```json +{ + "b": "x", + "c": "y/z" +} +``` + +## Making fetch requests + +To get data from an external API or a `+server.js` handler, you can use the provided `fetch` function, which behaves identically to the [native `fetch` web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) with a few additional features: + +- It can be used to make credentialed requests on the server, as it inherits the `cookie` and `authorization` headers for the page request. +- It can make relative requests on the server (ordinarily, `fetch` requires a URL with an origin when used in a server context). +- Internal requests (e.g. for `+server.js` routes) go directly to the handler function when running on the server, without the overhead of an HTTP call. +- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text`, `json` and `arrayBuffer` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](hooks#server-hooks-handle). +- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request - if you received a warning in your browser console when using the browser `fetch` instead of the `load` `fetch`, this is why. + +```js +/// file: src/routes/items/[id]/+page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ fetch, params }) { + const res = await fetch(`/api/items/${params.id}`); + const item = await res.json(); + + return { item }; +} +``` + +## Cookies + +A server `load` function can get and set [`cookies`](types#public-types-cookies). + +```js +/// file: src/routes/+layout.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getUser(sessionid: string | undefined): Promise<{ name: string, avatar: string }> +} + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/database'; + +/** @type {import('./$types').LayoutServerLoad} */ +export async function load({ cookies }) { + const sessionid = cookies.get('sessionid'); + + return { + user: await db.getUser(sessionid) + }; +} +``` + +Cookies will only be passed through the provided `fetch` function if the target host is the same as the SvelteKit application or a more specific subdomain of it. + +For example, if SvelteKit is serving my.domain.com: +- domain.com WILL NOT receive cookies +- my.domain.com WILL receive cookies +- api.domain.com WILL NOT receive cookies +- sub.my.domain.com WILL receive cookies + +Other cookies will not be passed when `credentials: 'include'` is set, because SvelteKit does not know which domain which cookie belongs to (the browser does not pass this information along), so it's not safe to forward any of them. Use the [handleFetch hook](hooks#server-hooks-handlefetch) to work around it. + +> When setting cookies, be aware of the `path` property. By default, the `path` of a cookie is the current pathname. If you for example set a cookie at page `admin/user`, the cookie will only be available within the `admin` pages by default. In most cases you likely want to set `path` to `'/'` to make the cookie available throughout your app. + +## Headers + +Both server and universal `load` functions have access to a `setHeaders` function that, when running on the server, can set headers for the response. (When running in the browser, `setHeaders` has no effect.) This is useful if you want the page to be cached, for example: + +```js +// @errors: 2322 1360 +/// file: src/routes/products/+page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ fetch, setHeaders }) { + const url = `https://cms.example.com/products.json`; + const response = await fetch(url); + + // cache the page for the same length of time + // as the underlying data + setHeaders({ + age: response.headers.get('age'), + 'cache-control': response.headers.get('cache-control') + }); + + return response.json(); +} +``` + +Setting the same header multiple times (even in separate `load` functions) is an error — you can only set a given header once. You cannot add a `set-cookie` header with `setHeaders` — use `cookies.set(name, value, options)` instead. + +## Using parent data + +Occasionally it's useful for a `load` function to access data from a parent `load` function, which can be done with `await parent()`: + +```js +/// file: src/routes/+layout.js +/** @type {import('./$types').LayoutLoad} */ +export function load() { + return { a: 1 }; +} +``` + +```js +/// file: src/routes/abc/+layout.js +/** @type {import('./$types').LayoutLoad} */ +export async function load({ parent }) { + const { a } = await parent(); + return { b: a + 1 }; +} +``` + +```js +/// file: src/routes/abc/+page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ parent }) { + const { a, b } = await parent(); + return { c: a + b }; +} +``` + +```svelte + + + + +

{data.a} + {data.b} = {data.c}

+``` + +> Notice that the `load` function in `+page.js` receives the merged data from both layout `load` functions, not just the immediate parent. + +Inside `+page.server.js` and `+layout.server.js`, `parent` returns data from parent `+layout.server.js` files. + +In `+page.js` or `+layout.js` it will return data from parent `+layout.js` files. However, a missing `+layout.js` is treated as a `({ data }) => data` function, meaning that it will also return data from parent `+layout.server.js` files that are not 'shadowed' by a `+layout.js` file + +Take care not to introduce waterfalls when using `await parent()`. Here, for example, `getData(params)` does not depend on the result of calling `parent()`, so we should call it first to avoid a delayed render. + +```diff +/// file: +page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ params, parent }) { +- const parentData = await parent(); + const data = await getData(params); ++ const parentData = await parent(); + + return { + ...data + meta: { ...parentData.meta, ...data.meta } + }; +} +``` + +## Errors + +If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For _expected_ errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message: + +```js +/// file: src/routes/admin/+layout.server.js +// @filename: ambient.d.ts +declare namespace App { + interface Locals { + user?: { + name: string; + isAdmin: boolean; + } + } +} + +// @filename: index.js +// ---cut--- +import { error } from '@sveltejs/kit'; + +/** @type {import('./$types').LayoutServerLoad} */ +export function load({ locals }) { + if (!locals.user) { + throw error(401, 'not logged in'); + } + + if (!locals.user.isAdmin) { + throw error(403, 'not an admin'); + } +} +``` + +If an _unexpected_ error is thrown, SvelteKit will invoke [`handleError`](hooks#shared-hooks-handleerror) and treat it as a 500 Internal Error. + +## Redirects + +To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the location to which they should be redirected alongside a `3xx` status code. + +```js +/// file: src/routes/user/+layout.server.js +// @filename: ambient.d.ts +declare namespace App { + interface Locals { + user?: { + name: string; + } + } +} + +// @filename: index.js +// ---cut--- +import { redirect } from '@sveltejs/kit'; + +/** @type {import('./$types').LayoutServerLoad} */ +export function load({ locals }) { + if (!locals.user) { + throw redirect(307, '/login'); + } +} +``` + +> Don't use `throw redirect()` from within a try-catch block, as the redirect will immediately trigger the catch statement. + +In the browser, you can also navigate programmatically outside of a `load` function using [`goto`](modules#$app-navigation-goto) from [`$app.navigation`](modules#$app-navigation). + +## Streaming with promises + +Promises at the _top level_ of the returned object will be awaited, making it easy to return multiple promises without creating a waterfall. When using a server `load`, _nested_ promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available: + +```js +/// file: src/routes/+page.server.js +/** @type {import('./$types').PageServerLoad} */ +export function load() { + return { + one: Promise.resolve(1), + two: Promise.resolve(2), + streamed: { + three: new Promise((fulfil) => { + setTimeout(() => { + fulfil(3) + }, 1000); + }) + } + }; +} +``` + +This is useful for creating skeleton loading states, for example: + +```svelte + + + +

+ one: {data.one} +

+

+ two: {data.two} +

+

+ three: + {#await data.streamed.three} + Loading... + {:then value} + {value} + {:catch error} + {error.message} + {/await} +

+``` + +When streaming data, be careful to handle promise rejections correctly. More specifically, the server could crash with an "unhandled promise rejection" error if a lazy-loaded promise fails before rendering starts (at which point it's caught) and isn't handling the error in some way. When using SvelteKit's `fetch` directly in the `load` function, SvelteKit will handle this case for you. For other promises, it is enough to attach a noop-`catch` to the promise to mark it as handled. + +```js +/// file: src/routes/+page.server.js +/** @type {import('./$types').PageServerLoad} */ +export function load({ fetch }) { + const ok_manual = Promise.reject(); + ok_manual.catch(() => {}); + + return { + streamed: { + ok_manual, + ok_fetch: fetch('/fetch/that/could/fail'), + dangerous_unhandled: Promise.reject() + } + }; +} +``` + +> On platforms that do not support streaming, such as AWS Lambda, responses will be buffered. This means the page will only render once all promises resolve. If you are using a proxy (e.g. NGINX), make sure it does not buffer responses from the proxied server. + +> Streaming data will only work when JavaScript is enabled. You should avoid returning nested promises from a universal `load` function if the page is server rendered, as these are _not_ streamed — instead, the promise is recreated when the function reruns in the browser. + +> The headers and status code of a response cannot be changed once the response has started streaming, therefore you cannot `setHeaders` or throw redirects inside a streamed promise. + +## Parallel loading + +When rendering (or navigating to) a page, SvelteKit runs all `load` functions concurrently, avoiding a waterfall of requests. During client-side navigation, the result of calling multiple server `load` functions are grouped into a single response. Once all `load` functions have returned, the page is rendered. + +## Rerunning load functions + +SvelteKit tracks the dependencies of each `load` function to avoid rerunning it unnecessarily during navigation. + +For example, given a pair of `load` functions like these... + +```js +/// file: src/routes/blog/[slug]/+page.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getPost(slug: string): Promise<{ title: string, content: string }> +} + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/database'; + +/** @type {import('./$types').PageServerLoad} */ +export async function load({ params }) { + return { + post: await db.getPost(params.slug) + }; +} +``` + +```js +/// file: src/routes/blog/[slug]/+layout.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getPostSummaries(): Promise> +} + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/database'; + +/** @type {import('./$types').LayoutServerLoad} */ +export async function load() { + return { + posts: await db.getPostSummaries() + }; +} +``` + +...the one in `+page.server.js` will rerun if we navigate from `/blog/trying-the-raw-meat-diet` to `/blog/i-regret-my-choices` because `params.slug` has changed. The one in `+layout.server.js` will not, because the data is still valid. In other words, we won't call `db.getPostSummaries()` a second time. + +A `load` function that calls `await parent()` will also rerun if a parent `load` function is rerun. + +Dependency tracking does not apply _after_ the `load` function has returned — for example, accessing `params.x` inside a nested [promise](#streaming-with-promises) will not cause the function to rerun when `params.x` changes. (Don't worry, you'll get a warning in development if you accidentally do this.) Instead, access the parameter in the main body of your `load` function. + +### Manual invalidation + +You can also rerun `load` functions that apply to the current page using [`invalidate(url)`](modules#$app-navigation-invalidate), which reruns all `load` functions that depend on `url`, and [`invalidateAll()`](modules#$app-navigation-invalidateall), which reruns every `load` function. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client. + +A `load` function depends on `url` if it calls `fetch(url)` or `depends(url)`. Note that `url` can be a custom identifier that starts with `[a-z]:`: + +```js +/// file: src/routes/random-number/+page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ fetch, depends }) { + // load reruns when `invalidate('https://api.example.com/random-number')` is called... + const response = await fetch('https://api.example.com/random-number'); + + // ...or when `invalidate('app:random')` is called + depends('app:random'); + + return { + number: await response.json() + }; +} +``` + +```svelte + + + +

random number: {data.number}

+ +``` + +### When do load functions rerun? + +To summarize, a `load` function will rerun in the following situations: + +- It references a property of `params` whose value has changed +- It references a property of `url` (such as `url.pathname` or `url.search`) whose value has changed. Properties in `request.url` are _not_ tracked +- It calls `await parent()` and a parent `load` function reran +- It declared a dependency on a specific URL via [`fetch`](#making-fetch-requests) (universal load only) or [`depends`](types#public-types-loadevent), and that URL was marked invalid with [`invalidate(url)`](modules#$app-navigation-invalidate) +- All active `load` functions were forcibly rerun with [`invalidateAll()`](modules#$app-navigation-invalidateall) + +`params` and `url` can change in response to a `` link click, a [`` interaction](form-actions#get-vs-post), a [`goto`](modules#$app-navigation-goto) invocation, or a [`redirect`](modules#sveltejs-kit-redirect). + +Note that rerunning a `load` function will update the `data` prop inside the corresponding `+layout.svelte` or `+page.svelte`; it does _not_ cause the component to be recreated. As a result, internal state is preserved. If this isn't what you want, you can reset whatever you need to reset inside an [`afterNavigate`](modules#$app-navigation-afternavigate) callback, and/or wrap your component in a [`{#key ...}`](https://svelte.dev/docs#template-syntax-key) block. + +## Implications for authentication + +A couple features of loading data have important implications for auth checks: +- Layout `load` functions do not run on every request, such as during client side navigation between child routes. [(When do load functions rerun?)](load#rerunning-load-functions-when-do-load-functions-rerun) +- Layout and page `load` functions run concurrently unless `await parent()` is called. If a layout `load` throws, the page `load` function runs, but the client will not receive the returned data. + +There are a few possible strategies to ensure an auth check occurs before protected code. + +To prevent data waterfalls and preserve layout `load` caches: +- Use [hooks](hooks) to protect multiple routes before any `load` functions run +- Use auth guards directly in `+page.server.js` `load` functions for route specific protection + +Putting an auth guard in `+layout.server.js` requires all child pages to call `await parent()` before protected code. Unless every child page depends on returned data from `await parent()`, the other options will be more performant. + +## Further reading + +- [Tutorial: Loading data](https://learn.svelte.dev/tutorial/page-data) +- [Tutorial: Errors and redirects](https://learn.svelte.dev/tutorial/error-basics) +- [Tutorial: Advanced loading](https://learn.svelte.dev/tutorial/await-parent) diff --git a/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/30-form-actions.md b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/30-form-actions.md new file mode 100644 index 0000000000..d25e141ead --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/30-form-actions.md @@ -0,0 +1,504 @@ +--- +title: Form actions +--- + +A `+page.server.js` file can export _actions_, which allow you to `POST` data to the server using the `` element. + +When using ``, client-side JavaScript is optional, but you can easily _progressively enhance_ your form interactions with JavaScript to provide the best user experience. + +## Default actions + +In the simplest case, a page declares a `default` action: + +```js +/// file: src/routes/login/+page.server.js +/** @type {import('./$types').Actions} */ +export const actions = { + default: async (event) => { + // TODO log the user in + } +}; +``` + +To invoke this action from the `/login` page, just add a `` — no JavaScript needed: + +```svelte + + + + + + +``` + +If someone were to click the button, the browser would send the form data via `POST` request to the server, running the default action. + +> Actions always use `POST` requests, since `GET` requests should never have side-effects. + +We can also invoke the action from other pages (for example if there's a login widget in the nav in the root layout) by adding the `action` attribute, pointing to the page: + +```html +/// file: src/routes/+layout.svelte +
+ +
+``` + +## Named actions + +Instead of one `default` action, a page can have as many named actions as it needs: + +```diff +/// file: src/routes/login/+page.server.js +/** @type {import('./$types').Actions} */ +export const actions = { +- default: async (event) => { ++ login: async (event) => { + // TODO log the user in + }, ++ register: async (event) => { ++ // TODO register the user ++ } +}; +``` + +To invoke a named action, add a query parameter with the name prefixed by a `/` character: + +```svelte + +
+``` + +```svelte + + +``` + +As well as the `action` attribute, we can use the `formaction` attribute on a button to `POST` the same form data to a different action than the parent ``: + +```diff +/// file: src/routes/login/+page.svelte +- ++ + + + ++ +
+``` + +> We can't have default actions next to named actions, because if you POST to a named action without a redirect, the query parameter is persisted in the URL, which means the next default POST would go through the named action from before. + +## Anatomy of an action + +Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the `form` property on the corresponding page and through `$page.form` app-wide until the next update. + +```js +// @errors: 2304 +/// file: src/routes/login/+page.server.js +/** @type {import('./$types').PageServerLoad} */ +export async function load({ cookies }) { + const user = await db.getUserFromSession(cookies.get('sessionid')); + return { user }; +} + +/** @type {import('./$types').Actions} */ +export const actions = { + login: async ({ cookies, request }) => { + const data = await request.formData(); + const email = data.get('email'); + const password = data.get('password'); + + const user = await db.getUser(email); + cookies.set('sessionid', await db.createSession(user)); + + return { success: true }; + }, + register: async (event) => { + // TODO register the user + } +}; +``` + +```svelte + + + +{#if form?.success} + +

Successfully logged in! Welcome back, {data.user.name}

+{/if} +``` + +### Validation errors + +If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `fail` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `$page.status` and the data through `form`: + +```diff +/// file: src/routes/login/+page.server.js ++import { fail } from '@sveltejs/kit'; + +/** @type {import('./$types').Actions} */ +export const actions = { + login: async ({ cookies, request }) => { + const data = await request.formData(); + const email = data.get('email'); + const password = data.get('password'); + ++ if (!email) { ++ return fail(400, { email, missing: true }); ++ } + + const user = await db.getUser(email); + ++ if (!user || user.password !== hash(password)) { ++ return fail(400, { email, incorrect: true }); ++ } + + cookies.set('sessionid', await db.createSession(user)); + + return { success: true }; + }, + register: async (event) => { + // TODO register the user + } +}; +``` + +> Note that as a precaution, we only return the email back to the page — not the password. + +```diff +/// file: src/routes/login/+page.svelte +
++ {#if form?.missing}

The email field is required

{/if} ++ {#if form?.incorrect}

Invalid credentials!

{/if} + + + + +
+``` + +The returned data must be serializable as JSON. Beyond that, the structure is entirely up to you. For example, if you had multiple forms on the page, you could distinguish which `
` the returned `form` data referred to with an `id` property or similar. + +### Redirects + +Redirects (and errors) work exactly the same as in [`load`](load#redirects): + +```diff +/// file: src/routes/login/+page.server.js ++import { fail, redirect } from '@sveltejs/kit'; + +/** @type {import('./$types').Actions} */ +export const actions = { ++ login: async ({ cookies, request, url }) => { + const data = await request.formData(); + const email = data.get('email'); + const password = data.get('password'); + + const user = await db.getUser(email); + if (!user) { + return fail(400, { email, missing: true }); + } + + if (user.password !== hash(password)) { + return fail(400, { email, incorrect: true }); + } + + cookies.set('sessionid', await db.createSession(user)); + ++ if (url.searchParams.has('redirectTo')) { ++ throw redirect(303, url.searchParams.get('redirectTo')); ++ } + + return { success: true }; + }, + register: async (event) => { + // TODO register the user + } +}; +``` + +## Loading data + +After an action runs, the page will be re-rendered (unless a redirect or an unexpected error occurs), with the action's return value available to the page as the `form` prop. This means that your page's `load` functions will run after the action completes. + +Note that `handle` runs before the action is invoked, and does not rerun before the `load` functions. This means that if, for example, you use `handle` to populate `event.locals` based on a cookie, you must update `event.locals` when you set or delete the cookie in an action: + +```js +/// file: src/hooks.server.js +// @filename: ambient.d.ts +declare namespace App { + interface Locals { + user: { + name: string; + } | null + } +} + +// @filename: global.d.ts +declare global { + function getUser(sessionid: string | undefined): { + name: string; + }; +} + +export {}; + +// @filename: index.js +// ---cut--- +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + event.locals.user = await getUser(event.cookies.get('sessionid')); + return resolve(event); +} +``` + +```js +/// file: src/routes/account/+page.server.js +// @filename: ambient.d.ts +declare namespace App { + interface Locals { + user: { + name: string; + } | null + } +} + +// @filename: index.js +// ---cut--- +/** @type {import('./$types').PageServerLoad} */ +export function load(event) { + return { + user: event.locals.user + }; +} + +/** @type {import('./$types').Actions} */ +export const actions = { + logout: async (event) => { + event.cookies.delete('sessionid'); + event.locals.user = null; + } +}; +``` + +## Progressive enhancement + +In the preceding sections we built a `/login` action that [works without client-side JavaScript](https://kryogenix.org/code/browser/everyonehasjs.html) — not a `fetch` in sight. That's great, but when JavaScript _is_ available we can progressively enhance our form interactions to provide a better user experience. + +### use:enhance + +The easiest way to progressively enhance a form is to add the `use:enhance` action: + +```diff +/// file: src/routes/login/+page.svelte + + ++ +``` + +> Yes, it's a little confusing that the `enhance` action and `` are both called 'action'. These docs are action-packed. Sorry. + +Without an argument, `use:enhance` will emulate the browser-native behaviour, just without the full-page reloads. It will: + +- update the `form` property, `$page.form` and `$page.status` on a successful or invalid response, but only if the action is on the same page you're submitting from. For example, if your form looks like ``, `form` and `$page` will _not_ be updated. This is because in the native form submission case you would be redirected to the page the action is on. If you want to have them updated either way, use [`applyAction`](#progressive-enhancement-customising-use-enhance) +- reset the `` element +- invalidate all data using `invalidateAll` on a successful response +- call `goto` on a redirect response +- render the nearest `+error` boundary if an error occurs +- [reset focus](accessibility#focus-management) to the appropriate element + +### Customising use:enhance + +To customise the behaviour, you can provide a `SubmitFunction` that runs immediately before the form is submitted, and (optionally) returns a callback that runs with the `ActionResult`. Note that if you return a callback, the default behavior mentioned above is not triggered. To get it back, call `update`. + +```svelte + { + // `formElement` is this `` element + // `formData` is its `FormData` object that's about to be submitted + // `action` is the URL to which the form is posted + // calling `cancel()` will prevent the submission + // `submitter` is the `HTMLElement` that caused the form to be submitted + + return async ({ result, update }) => { + // `result` is an `ActionResult` object + // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set + }; + }} +> +``` + +You can use these functions to show and hide loading UI, and so on. + +If you return a callback, you may need to reproduce part of the default `use:enhance` behaviour, but without invalidating all data on a successful response. You can do so with `applyAction`: + +```diff +/// file: src/routes/login/+page.svelte + + + { + + return async ({ result }) => { + // `result` is an `ActionResult` object ++ if (result.type === 'redirect') { ++ goto(result.location); ++ } else { ++ await applyAction(result); ++ } + }; + }} +> +``` + +The behaviour of `applyAction(result)` depends on `result.type`: + +- `success`, `failure` — sets `$page.status` to `result.status` and updates `form` and `$page.form` to `result.data` (regardless of where you are submitting from, in contrast to `update` from `enhance`) +- `redirect` — calls `goto(result.location, { invalidateAll: true })` +- `error` — renders the nearest `+error` boundary with `result.error` + +In all cases, [focus will be reset](accessibility#focus-management). + +### Custom event listener + +We can also implement progressive enhancement ourselves, without `use:enhance`, with a normal event listener on the ``: + +```svelte + + + + + +
+``` + +Note that you need to `deserialize` the response before processing it further using the corresponding method from `$app/forms`. `JSON.parse()` isn't enough because form actions - like `load` functions - also support returning `Date` or `BigInt` objects. + +If you have a `+server.js` alongside your `+page.server.js`, `fetch` requests will be routed there by default. To `POST` to an action in `+page.server.js` instead, use the custom `x-sveltekit-action` header: + +```diff +const response = await fetch(this.action, { + method: 'POST', + body: data, ++ headers: { ++ 'x-sveltekit-action': 'true' ++ } +}); +``` + +## Alternatives + +Form actions are the preferred way to send data to the server, since they can be progressively enhanced, but you can also use [`+server.js`](routing#server) files to expose (for example) a JSON API. Here's how such an interaction could look like: + +```svelte + + + + +``` + +```js +// @errors: 2355 1360 2322 +/// file: api/ci/+server.js + +/** @type {import('./$types').RequestHandler} */ +export function POST() { + // do something +} +``` + +## GET vs POST + +As we've seen, to invoke a form action you must use `method="POST"`. + +Some forms don't need to `POST` data to the server — search inputs, for example. For these you can use `method="GET"` (or, equivalently, no `method` at all), and SvelteKit will treat them like `
` elements, using the client-side router instead of a full page navigation: + +```html +
+ +
+``` + +Submitting this form will navigate to `/search?q=...` and invoke your load function but will not invoke an action. As with `
` elements, you can set the [`data-sveltekit-reload`](link-options#data-sveltekit-reload), [`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate), [`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) and [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) attributes on the `
` to control the router's behaviour. + +## Further reading + +- [Tutorial: Forms](https://learn.svelte.dev/tutorial/the-form-element) diff --git a/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/40-page-options.md b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/40-page-options.md new file mode 100644 index 0000000000..ecd93dcc2c --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/40-page-options.md @@ -0,0 +1,211 @@ +--- +title: Page options +--- + +By default, SvelteKit will render (or [prerender](glossary#prerendering)) any component first on the server and send it to the client as HTML. It will then render the component again in the browser to make it interactive in a process called [**hydration**](glossary#hydration). For this reason, you need to ensure that components can run in both places. SvelteKit will then initialize a [**router**](routing) that takes over subsequent navigations. + +You can control each of these on a page-by-page basis by exporting options from [`+page.js`](routing#page-page-js) or [`+page.server.js`](routing#page-page-server-js), or for groups of pages using a shared [`+layout.js`](routing#layout-layout-js) or [`+layout.server.js`](routing#layout-layout-server-js). To define an option for the whole app, export it from the root layout. Child layouts and pages override values set in parent layouts, so — for example — you can enable prerendering for your entire app then disable it for pages that need to be dynamically rendered. + +You can mix and match these options in different areas of your app. For example you could prerender your marketing page for maximum speed, server-render your dynamic pages for SEO and accessibility and turn your admin section into an SPA by rendering it on the client only. This makes SvelteKit very versatile. + +## prerender + +It's likely that at least some routes of your app can be represented as a simple HTML file generated at build time. These routes can be [_prerendered_](glossary#prerendering). + +```js +/// file: +page.js/+page.server.js/+server.js +export const prerender = true; +``` + +Alternatively, you can set `export const prerender = true` in your root `+layout.js` or `+layout.server.js` and prerender everything except pages that are explicitly marked as _not_ prerenderable: + +```js +/// file: +page.js/+page.server.js/+server.js +export const prerender = false; +``` + +Routes with `prerender = true` will be excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller. In some cases you might want to prerender a route but also include it in the manifest (for example, with a route like `/blog/[slug]` where you want to prerender your most recent/popular content but server-render the long tail) — for these cases, there's a third option, 'auto': + +```js +/// file: +page.js/+page.server.js/+server.js +export const prerender = 'auto'; +``` + +> If your entire app is suitable for prerendering, you can use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static), which will output files suitable for use with any static webserver. + +The prerenderer will start at the root of your app and generate files for any prerenderable pages or `+server.js` routes it finds. Each page is scanned for `` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with [`config.kit.prerender.entries`](configuration#prerender), or by exporting an [`entries`](#entries) function from your dynamic route. + +While prerendering, the value of `building` imported from [`$app/environment`](modules#$app-environment) will be `true`. + +### Prerendering server routes + +Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected by layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function... + +```js +/// file: +page.js +export const prerender = true; + +/** @type {import('./$types').PageLoad} */ +export async function load({ fetch }) { + const res = await fetch('/my-server-route.json'); + return await res.json(); +} +``` + +...then `src/routes/my-server-route.json/+server.js` will be treated as prerenderable if it doesn't contain its own `export const prerender = false`. + +### When not to prerender + +The basic rule is this: for a page to be prerenderable, any two users hitting it directly must get the same content from the server. + +> Not all pages are suitable for prerendering. Any content that is prerendered will be seen by all users. You can of course fetch personalized data in `onMount` in a prerendered page, but this may result in a poorer user experience since it will involve blank initial content or loading indicators. + +Note that you can still prerender pages that load data based on the page's parameters, such as a `src/routes/blog/[slug]/+page.svelte` route. + +Accessing [`url.searchParams`](load#using-url-data-url) during prerendering is forbidden. If you need to use it, ensure you are only doing so in the browser (for example in `onMount`). + +Pages with [actions](form-actions) cannot be prerendered, because a server must be able to handle the action `POST` requests. + +### Route conflicts + +Because prerendering writes to the filesystem, it isn't possible to have two endpoints that would cause a directory and a file to have the same name. For example, `src/routes/foo/+server.js` and `src/routes/foo/bar/+server.js` would try to create `foo` and `foo/bar`, which is impossible. + +For that reason among others, it's recommended that you always include a file extension — `src/routes/foo.json/+server.js` and `src/routes/foo/bar.json/+server.js` would result in `foo.json` and `foo/bar.json` files living harmoniously side-by-side. + +For _pages_, we skirt around this problem by writing `foo/index.html` instead of `foo`. + +### Troubleshooting + +If you encounter an error like 'The following routes were marked as prerenderable, but were not prerendered' it's because the route in question (or a parent layout, if it's a page) has `export const prerender = true` but the page wasn't actually prerendered, because it wasn't reached by the prerendering crawler. + +Since these routes cannot be dynamically server-rendered, this will cause errors when people try to access the route in question. There are two ways to fix it: + +* Ensure that SvelteKit can find the route by following links from [`config.kit.prerender.entries`](configuration#prerender) or the [`entries`](#entries) page option. Add links to dynamic routes (i.e. pages with `[parameters]` ) to this option if they are not found through crawling the other entry points, else they are not prerendered because SvelteKit doesn't know what value the parameters should have. Pages not marked as prerenderable will be ignored and their links to other pages will not be crawled, even if some of them would be prerenderable. +* Change `export const prerender = true` to `export const prerender = 'auto'`. Routes with `'auto'` can be dynamically server rendered + +## entries + +SvelteKit will discover pages to prerender automatically, by starting at _entry points_ and crawling them. By default, all your non-dynamic routes are considered entry points — for example, if you have these routes... + +```bash +/ # non-dynamic +/blog # non-dynamic +/blog/[slug] # dynamic, because of `[slug]` +``` + +...SvelteKit will prerender `/` and `/blog`, and in the process discover links like `` which give it new pages to prerender. + +Most of the time, that's enough. In some situations, links to pages like `/blog/hello-world` might not exist (or might not exist on prerendered pages), in which case we need to tell SvelteKit about their existence. + +This can be done with [`config.kit.prerender.entries`](configuration#prerender), or by exporting an `entries` function from a `+page.js`, a `+page.server.js` or a `+server.js` belonging to a dynamic route: + +```js +/// file: src/routes/blog/[slug]/+page.server.js +/** @type {import('./$types').EntryGenerator} */ +export function entries() { + return [ + { slug: 'hello-world' }, + { slug: 'another-blog-post' } + ]; +} + +export const prerender = true; +``` + +`entries` can be an `async` function, allowing you to (for example) retrieve a list of posts from a CMS or database, in the example above. + +## ssr + +Normally, SvelteKit renders your page on the server first and sends that HTML to the client where it's [hydrated](glossary#hydration). If you set `ssr` to `false`, it renders an empty 'shell' page instead. This is useful if your page is unable to be rendered on the server (because you use browser-only globals like `document` for example), but in most situations it's not recommended ([see appendix](glossary#ssr)). + +```js +/// file: +page.js +export const ssr = false; +// If both `ssr` and `csr` are `false`, nothing will be rendered! +``` + +If you add `export const ssr = false` to your root `+layout.js`, your entire app will only be rendered on the client — which essentially means you turn your app into an SPA. + +## csr + +Ordinarily, SvelteKit [hydrates](glossary#hydration) your server-rendered HTML into an interactive client-side-rendered (CSR) page. Some pages don't require JavaScript at all — many blog posts and 'about' pages fall into this category. In these cases you can disable CSR: + +```js +/// file: +page.js +export const csr = false; +// If both `csr` and `ssr` are `false`, nothing will be rendered! +``` + +Disabling CSR does not ship any JavaScript to the client. This means: + +* The webpage should work with HTML and CSS only. +* ` +``` + +```svelte + + + +

Welcome {$user.name}

+``` + +Updating the context-based store value in deeper-level pages or components will not affect the value in the parent component when the page is rendered via SSR: The parent component has already been rendered by the time the store value is updated. To avoid values 'flashing' during state updates during hydration, it is generally recommended to pass state down into components rather than up. + +If you're not using SSR (and can guarantee that you won't need to use SSR in future) then you can safely keep state in a shared module, without using the context API. + +## Component and page state is preserved + +When you navigate around your application, SvelteKit reuses existing layout and page components. For example, if you have a route like this... + +```svelte + + + +
+

{data.title}

+

Reading time: {Math.round(estimatedReadingTime)} minutes

+
+ +
{@html data.content}
+``` + +...then navigating from `/blog/my-short-post` to `/blog/my-long-post` won't cause the layout, page and any other components within to be destroyed and recreated. Instead the `data` prop (and by extension `data.title` and `data.content`) will update (as it would with any other Svelte component) and, because the code isn't rerunning, lifecycle methods like `onMount` and `onDestroy` won't rerun and `estimatedReadingTime` won't be recalculated. + +Instead, we need to make the value [_reactive_](https://learn.svelte.dev/tutorial/reactive-assignments): + +```diff +/// file: src/routes/blog/[slug]/+page.svelte + +``` + +> If your code in `onMount` and `onDestroy` has to run again after navigation you can use [afterNavigate](modules#$app-navigation-afternavigate) and [beforeNavigate](modules#$app-navigation-beforenavigate) respectively. + +Reusing components like this means that things like sidebar scroll state are preserved, and you can easily animate between changing values. In the case that you do need to completely destroy and remount a component on navigation, you can use this pattern: + +```svelte +{#key $page.url.pathname} + +{/key} +``` + +## Storing state in the URL + +If you have state that should survive a reload and/or affect SSR, such as filters or sorting rules on a table, URL search parameters (like `?sort=price&order=ascending`) are a good place to put them. You can put them in `
` or `` attributes, or set them programmatically via `goto('?key=value')`. They can be accessed inside `load` functions via the `url` parameter, and inside components via `$page.url.searchParams`. + +## Storing ephemeral state in snapshots + +Some UI state, such as 'is the accordion open?', is disposable — if the user navigates away or refreshes the page, it doesn't matter if the state is lost. In some cases, you _do_ want the data to persist if the user navigates to a different page and comes back, but storing the state in the URL or in a database would be overkill. For this, SvelteKit provides [snapshots](snapshots), which let you associate component state with a history entry. diff --git a/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/index.md b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/index.md new file mode 100644 index 0000000000..0e75c5a862 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/20-core-concepts/index.md @@ -0,0 +1,3 @@ +--- +title: "Core concepts" +--- \ No newline at end of file diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/10-building-your-app.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/10-building-your-app.md new file mode 100644 index 0000000000..d7c4cb71b5 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/10-building-your-app.md @@ -0,0 +1,30 @@ +--- +title: Building your app +--- + +Building a SvelteKit app happens in two stages, which both happen when you run `vite build` (usually via `npm run build`). + +Firstly, Vite creates an optimized production build of your server code, your browser code, and your service worker (if you have one). [Prerendering](page-options#prerender) is executed at this stage, if appropriate. + +Secondly, an _adapter_ takes this production build and tunes it for your target environment — more on this on the following pages. + +## During the build + +SvelteKit will load your `+page/layout(.server).js` files (and all files they import) for analysis during the build. Any code that should _not_ be executed at this stage must check that `building` from [`$app/environment`](modules#$app-environment) is `false`: + +```diff ++import { building } from '$app/environment'; +import { setupMyDatabase } from '$lib/server/database'; + ++if (!building) { + setupMyDatabase(); ++} + +export function load() { + // ... +} +``` + +## Preview your app + +After building, you can view your production build locally with `vite preview` (via `npm run preview`). Note that this will run the app in Node, and so is not a perfect reproduction of your deployed app — adapter-specific adjustments like the [`platform` object](adapters#platform-specific-context) do not apply to previews. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/20-adapters.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/20-adapters.md new file mode 100644 index 0000000000..15acd012fd --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/20-adapters.md @@ -0,0 +1,49 @@ +--- +title: Adapters +--- + +Before you can deploy your SvelteKit app, you need to _adapt_ it for your deployment target. Adapters are small plugins that take the built app as input and generate output for deployment. + +Official adapters exist for a variety of platforms — these are documented on the following pages: + +- [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) for Cloudflare Pages +- [`@sveltejs/adapter-cloudflare-workers`](adapter-cloudflare-workers) for Cloudflare Workers +- [`@sveltejs/adapter-netlify`](adapter-netlify) for Netlify +- [`@sveltejs/adapter-node`](adapter-node) for Node servers +- [`@sveltejs/adapter-static`](adapter-static) for static site generation (SSG) +- [`@sveltejs/adapter-vercel`](adapter-vercel) for Vercel + +Additional [community-provided adapters](https://sveltesociety.dev/components#adapters) exist for other platforms. + +## Using adapters + +Your adapter is specified in `svelte.config.js`: + +```js +/// file: svelte.config.js +// @filename: ambient.d.ts +declare module 'svelte-adapter-foo' { + const adapter: (opts: any) => import('@sveltejs/kit').Adapter; + export default adapter; +} + +// @filename: index.js +// ---cut--- +import adapter from 'svelte-adapter-foo'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + // adapter options go here + }) + } +}; + +export default config; +``` + +## Platform-specific context + +Some adapters may have access to additional information about the request. For example, Cloudflare Workers can access an `env` object containing KV namespaces etc. This can be passed to the `RequestEvent` used in [hooks](hooks) and [server routes](routing#server) as the `platform` property — consult each adapter's documentation to learn more. + diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/30-adapter-auto.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/30-adapter-auto.md new file mode 100644 index 0000000000..e52f5967bd --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/30-adapter-auto.md @@ -0,0 +1,21 @@ +--- +title: Zero-config deployments +--- + +When you create a new SvelteKit project with `npm create svelte@latest`, it installs [`adapter-auto`](https://github.com/sveltejs/kit/tree/master/packages/adapter-auto) by default. This adapter automatically installs and uses the correct adapter for supported environments when you deploy: + +- [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) for [Cloudflare Pages](https://developers.cloudflare.com/pages/) +- [`@sveltejs/adapter-netlify`](adapter-netlify) for [Netlify](https://netlify.com/) +- [`@sveltejs/adapter-vercel`](adapter-vercel) for [Vercel](https://vercel.com/) +- [`svelte-adapter-azure-swa`](https://github.com/geoffrich/svelte-adapter-azure-swa) for [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/) +- [`svelte-kit-sst`](https://github.com/serverless-stack/sst/tree/master/packages/svelte-kit-sst) for [AWS via SST](https://docs.sst.dev/start/svelte) + +It's recommended to install the appropriate adapter to your `devDependencies` once you've settled on a target environment, since this will add the adapter to your lockfile and slightly improve install times on CI. + +## Environment-specific configuration + +To add configuration options, such as `{ edge: true }` in [`adapter-vercel`](adapter-vercel) and [`adapter-netlify`](adapter-netlify), you must install the underlying adapter — `adapter-auto` does not take any options. + +## Adding community adapters + +You can add zero-config support for additional adapters by editing [adapters.js](https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/adapters.js) and opening a pull request. \ No newline at end of file diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/40-adapter-node.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/40-adapter-node.md new file mode 100644 index 0000000000..f3c04bfa85 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/40-adapter-node.md @@ -0,0 +1,212 @@ +--- +title: Node servers +--- + +To generate a standalone Node server, use [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node). + +## Usage + +Install with `npm i -D @sveltejs/adapter-node`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-node'; + +export default { + kit: { + adapter: adapter() + } +}; +``` + +## Deploying + +First, build your app with `npm run build`. This will create the production server in the output directory specified in the adapter options, defaulting to `build`. + +You will need the output directory, the project's `package.json`, and the production dependencies in `node_modules` to run the application. Production dependencies can be generated by copying the `package.json` and `package-lock.json` and then running `npm ci --omit dev` (you can skip this step if your app doesn't have any dependencies). You can then start your app with this command: + +```bash +node build +``` + +Development dependencies will be bundled into your app using [Rollup](https://rollupjs.org). To control whether a given package is bundled or externalised, place it in `devDependencies` or `dependencies` respectively in your `package.json`. + +## Environment variables + +In `dev` and `preview`, SvelteKit will read environment variables from your `.env` file (or `.env.local`, or `.env.[mode]`, [as determined by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files).) + +In production, `.env` files are _not_ automatically loaded. To do so, install `dotenv` in your project... + +```bash +npm install dotenv +``` + +...and invoke it before running the built app: + +```diff +-node build ++node -r dotenv/config build +``` + +### `PORT`, `HOST` and `SOCKET_PATH` + +By default, the server will accept connections on `0.0.0.0` using port 3000. These can be customised with the `PORT` and `HOST` environment variables: + +``` +HOST=127.0.0.1 PORT=4000 node build +``` + +Alternatively, the server can be configured to accept connections on a specified socket path. When this is done using the `SOCKET_PATH` environment variable, the `HOST` and `PORT` environment variables will be disregarded. + +``` +SOCKET_PATH=/tmp/socket node build +``` + +### `ORIGIN`, `PROTOCOL_HEADER` and `HOST_HEADER` + +HTTP doesn't give SvelteKit a reliable way to know the URL that is currently being requested. The simplest way to tell SvelteKit where the app is being served is to set the `ORIGIN` environment variable: + +``` +ORIGIN=https://my.site node build + +# or e.g. for local previewing and testing +ORIGIN=http://localhost:3000 node build +``` + +With this, a request for the `/stuff` pathname will correctly resolve to `https://my.site/stuff`. Alternatively, you can specify headers that tell SvelteKit about the request protocol and host, from which it can construct the origin URL: + +``` +PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build +``` + +> [`x-forwarded-proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) and [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) are de facto standard headers that forward the original protocol and host if you're using a reverse proxy (think load balancers and CDNs). You should only set these variables if your server is behind a trusted reverse proxy; otherwise, it'd be possible for clients to spoof these headers. + +If `adapter-node` can't correctly determine the URL of your deployment, you may experience this error when using [form actions](form-actions): + +> Cross-site POST form submissions are forbidden + +### `ADDRESS_HEADER` and `XFF_DEPTH` + +The [RequestEvent](types#public-types-requestevent) object passed to hooks and endpoints includes an `event.getClientAddress()` function that returns the client's IP address. By default this is the connecting `remoteAddress`. If your server is behind one or more proxies (such as a load balancer), this value will contain the innermost proxy's IP address rather than the client's, so we need to specify an `ADDRESS_HEADER` to read the address from: + +``` +ADDRESS_HEADER=True-Client-IP node build +``` + +> Headers can easily be spoofed. As with `PROTOCOL_HEADER` and `HOST_HEADER`, you should [know what you're doing](https://adam-p.ca/blog/2022/03/x-forwarded-for/) before setting these. + +If the `ADDRESS_HEADER` is `X-Forwarded-For`, the header value will contain a comma-separated list of IP addresses. The `XFF_DEPTH` environment variable should specify how many trusted proxies sit in front of your server. E.g. if there are three trusted proxies, proxy 3 will forward the addresses of the original connection and the first two proxies: + +``` +, , +``` + +Some guides will tell you to read the left-most address, but this leaves you [vulnerable to spoofing](https://adam-p.ca/blog/2022/03/x-forwarded-for/): + +``` +, , , +``` + +We instead read from the _right_, accounting for the number of trusted proxies. In this case, we would use `XFF_DEPTH=3`. + +> If you need to read the left-most address instead (and don't care about spoofing) — for example, to offer a geolocation service, where it's more important for the IP address to be _real_ than _trusted_, you can do so by inspecting the `x-forwarded-for` header within your app. + +### `BODY_SIZE_LIMIT` + +The maximum request body size to accept in bytes including while streaming. Defaults to 512kb. You can disable this option with a value of 0 and implement a custom check in [`handle`](hooks#server-hooks-handle) if you need something more advanced. + +## Options + +The adapter can be configured with various options: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-node'; + +export default { + kit: { + adapter: adapter({ + // default options are shown + out: 'build', + precompress: false, + envPrefix: '', + polyfill: true + }) + } +}; +``` + +### out + +The directory to build the server to. It defaults to `build` — i.e. `node build` would start the server locally after it has been created. + +### precompress + +Enables precompressing using gzip and brotli for assets and prerendered pages. It defaults to `false`. + +### envPrefix + +If you need to change the name of the environment variables used to configure the deployment (for example, to deconflict with environment variables you don't control), you can specify a prefix: + +```js +envPrefix: 'MY_CUSTOM_'; +``` + +```sh +MY_CUSTOM_HOST=127.0.0.1 \ +MY_CUSTOM_PORT=4000 \ +MY_CUSTOM_ORIGIN=https://my.site \ +node build +``` + +### polyfill + +Controls whether your build will load polyfills for missing modules. It defaults to `true`, and should only be disabled when using Node 18.11 or greater. + +Note: to use Node's built-in `crypto` global with Node 18 you will need to use the `--experimental-global-webcrypto` flag. This flag is not required with Node 20. + +## Custom server + +The adapter creates two files in your build directory — `index.js` and `handler.js`. Running `index.js` — e.g. `node build`, if you use the default build directory — will start a server on the configured port. + +Alternatively, you can import the `handler.js` file, which exports a handler suitable for use with [Express](https://github.com/expressjs/express), [Connect](https://github.com/senchalabs/connect) or [Polka](https://github.com/lukeed/polka) (or even just the built-in [`http.createServer`](https://nodejs.org/dist/latest/docs/api/http.html#httpcreateserveroptions-requestlistener)) and set up your own server: + +```js +// @errors: 2307 7006 +/// file: my-server.js +import { handler } from './build/handler.js'; +import express from 'express'; + +const app = express(); + +// add a route that lives separately from the SvelteKit app +app.get('/healthcheck', (req, res) => { + res.end('ok'); +}); + +// let SvelteKit handle everything else, including serving prerendered pages and static assets +app.use(handler); + +app.listen(3000, () => { + console.log('listening on port 3000'); +}); +``` + +## Troubleshooting + +### Is there a hook for cleaning up before the server exits? + +There's nothing built-in to SvelteKit for this, because such a cleanup hook depends highly on the execution environment you're on. For Node, you can use its built-in `process.on(..)` to implement a callback that runs before the server exits: + +```js +// @errors: 2304 2580 +function shutdownGracefully() { + // anything you need to clean up manually goes in here + db.shutdown(); +} + +process.on('SIGINT', shutdownGracefully); +process.on('SIGTERM', shutdownGracefully); +``` diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/50-adapter-static.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/50-adapter-static.md new file mode 100644 index 0000000000..479ce083f4 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/50-adapter-static.md @@ -0,0 +1,171 @@ +--- +title: Static site generation +--- + +To use SvelteKit as a static site generator (SSG), use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static). + +This will prerender your entire site as a collection of static files. If you'd like to prerender only some pages and dynamically server-render others, you will need to use a different adapter together with [the `prerender` option](page-options#prerender). + +## Usage + +Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-static'; + +export default { + kit: { + adapter: adapter({ + // default options are shown. On some platforms + // these options are set automatically — see below + pages: 'build', + assets: 'build', + fallback: undefined, + precompress: false, + strict: true + }) + } +}; +``` + +...and add the [`prerender`](page-options#prerender) option to your root layout: + +```js +/// file: src/routes/+layout.js +// This can be false if you're using a fallback (i.e. SPA mode) +export const prerender = true; +``` + +> You must ensure SvelteKit's [`trailingSlash`](page-options#trailingslash) option is set appropriately for your environment. If your host does not render `/a.html` upon receiving a request for `/a` then you will need to set `trailingSlash: 'always'` in your root layout to create `/a/index.html` instead. + +## Zero-config support + +Some platforms have zero-config support (more to come in future): + +- [Vercel](https://vercel.com) + +On these platforms, you should omit the adapter options so that `adapter-static` can provide the optimal configuration: + +```diff +/// file: svelte.config.js +export default { + kit: { +- adapter: adapter({...}) ++ adapter: adapter() + } +}; +``` + +## Options + +### pages + +The directory to write prerendered pages to. It defaults to `build`. + +### assets + +The directory to write static assets (the contents of `static`, plus client-side JS and CSS generated by SvelteKit) to. Ordinarily this should be the same as `pages`, and it will default to whatever the value of `pages` is, but in rare circumstances you might need to output pages and assets to separate locations. + +### fallback + +Specify a fallback page for [SPA mode](single-page-apps), e.g. `index.html` or `200.html` or `404.html`. + +### precompress + +If `true`, precompresses files with brotli and gzip. This will generate `.br` and `.gz` files. + +### strict + +By default, `adapter-static` checks that either all pages and endpoints (if any) of your app were prerendered, or you have the `fallback` option set. This check exists to prevent you from accidentally publishing an app where some parts of it are not accessible, because they are not contained in the final output. If you know this is ok (for example when a certain page only exists conditionally), you can set `strict` to `false` to turn off this check. + +## GitHub Pages + +When building for [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages), if your repo name is not equivalent to `your-username.github.io`, make sure to update [`config.kit.paths.base`](configuration#paths) to match your repo name. This is because the site will be served from `https://your-username.github.io/your-repo-name` rather than from the root. + +You'll also want to generate a fallback `404.html` page to replace the default 404 page shown by GitHub Pages. + +A config for GitHub Pages might look like the following: + +```js +// @errors: 2307 2322 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-static'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + fallback: '404.html' + }), + paths: { + base: process.argv.includes('dev') ? '' : process.env.BASE_PATH + } + } +}; + +export default config; +``` + +You can use GitHub actions to automatically deploy your site to GitHub Pages when you make a change. Here's an example workflow: + +```yaml +### file: .github/workflows/deploy.yml +name: Deploy to GitHub Pages + +on: + push: + branches: 'main' + +jobs: + build_site: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + # If you're using pnpm, add this step then change the commands and cache key below to use `pnpm` + # - name: Install pnpm + # uses: pnpm/action-setup@v2 + # with: + # version: 8 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: npm + + - name: Install dependencies + run: npm install + + - name: build + env: + BASE_PATH: '/${{ github.event.repository.name }}' + run: | + npm run build + + - name: Upload Artifacts + uses: actions/upload-pages-artifact@v2 + with: + # this should match the `pages` option in your adapter-static options + path: 'build/' + + deploy: + needs: build_site + runs-on: ubuntu-latest + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v2 +``` diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/55-single-page-apps.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/55-single-page-apps.md new file mode 100644 index 0000000000..efc73ca7a9 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/55-single-page-apps.md @@ -0,0 +1,63 @@ +--- +title: Single-page apps +--- + +You can turn any SvelteKit app, using any adapter, into a fully client-rendered single-page app (SPA) by disabling SSR at the root layout: + +```js +/// file: src/routes/+layout.js +export const ssr = false; +``` + +> In most situations this is not recommended: it harms SEO, tends to slow down perceived performance, and makes your app inaccessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). + +If you don't have any server-side logic (i.e. `+page.server.js`, `+layout.server.js` or `+server.js` files) you can use [`adapter-static`](adapter-static) to create your SPA by adding a _fallback page_. + +## Usage + +Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js` with the following options: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-static'; + +export default { + kit: { + adapter: adapter({ + fallback: '200.html' // may differ from host to host + }) + } +}; +``` + +The `fallback` page is an HTML page created by SvelteKit from your page template (e.g. `app.html`) that loads your app and navigates to the correct route. For example [Surge](https://surge.sh/help/adding-a-200-page-for-client-side-routing), a static web host, lets you add a `200.html` file that will handle any requests that don't correspond to static assets or prerendered pages. + +On some hosts it may be `index.html` or something else entirely — consult your platform's documentation. + +> Note that the fallback page will always contain absolute asset paths (i.e. beginning with `/` rather than `.`) regardless of the value of [`paths.relative`](/docs/configuration#paths), since it is used to respond to requests for arbitrary paths. + +## Apache + +To run an SPA on [Apache](https://httpd.apache.org/), you should add a `static/.htaccess` file to route requests to the fallback page: + +``` + + RewriteEngine On + RewriteBase / + RewriteRule ^200\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /200.html [L] + +``` + +## Prerendering individual pages + +If you want certain pages to be prerendered, you can re-enable `ssr` alongside `prerender` for just those parts of your app: + +```js +/// file: src/routes/my-prerendered-page/+page.js +export const prerender = true; +export const ssr = true; +``` diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/60-adapter-cloudflare.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/60-adapter-cloudflare.md new file mode 100644 index 0000000000..e929ae55cc --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/60-adapter-cloudflare.md @@ -0,0 +1,111 @@ +--- +title: Cloudflare Pages +--- + +To deploy to [Cloudflare Pages](https://developers.cloudflare.com/pages/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare). + +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare Pages you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that type declarations will be automatically applied and you can set Cloudflare-specific options. + +## Comparisons + +- `adapter-cloudflare` – supports all SvelteKit features; builds for [Cloudflare Pages](https://blog.cloudflare.com/cloudflare-pages-goes-full-stack/) +- `adapter-cloudflare-workers` – supports all SvelteKit features; builds for Cloudflare Workers +- `adapter-static` – only produces client-side static assets; compatible with Cloudflare Pages + +## Usage + +Install with `npm i -D @sveltejs/adapter-cloudflare`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-cloudflare'; + +export default { + kit: { + adapter: adapter({ + // See below for an explanation of these options + routes: { + include: ['/*'], + exclude: [''] + } + }) + } +}; +``` + +## Options + +The `routes` option allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. + +- `include` defines routes that will invoke a function, and defaults to `['/*']` +- `exclude` defines routes that will _not_ invoke a function — this is a faster and cheaper way to serve your app's static assets. This array can include the following special values: + - `` contains your app's build artifacts (the files generated by Vite) + - `` contains the contents of your `static` directory + - `` contains a list of prerendered pages + - `` (the default) contains all of the above + +You can have up to 100 `include` and `exclude` rules combined. Generally you can omit the `routes` options, but if (for example) your `` paths exceed that limit, you may find it helpful to manually create an `exclude` list that includes `'/articles/*'` instead of the auto-generated `['/articles/foo', '/articles/bar', '/articles/baz', ...]`. + +## Deployment + +Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started) for Cloudflare Pages to begin. + +When configuring your project settings, you must use the following settings: + +- **Framework preset** – SvelteKit +- **Build command** – `npm run build` or `vite build` +- **Build output directory** – `.svelte-kit/cloudflare` + +## Bindings + +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/pages/platform/functions/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with `context` and `caches`, meaning that you can access it in hooks and endpoints: + +```js +// @errors: 7031 +export async function POST({ request, platform }) { + const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); +} +``` + +> SvelteKit's built-in `$env` module should be preferred for environment variables. + +To make these types available to your app, reference them in your `src/app.d.ts`: + +```diff +/// file: src/app.d.ts +declare global { + namespace App { + interface Platform { ++ env?: { ++ YOUR_KV_NAMESPACE: KVNamespace; ++ YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; ++ }; + } + } +} + +export {}; +``` + +### Testing Locally + +`platform.env` is only available in the final build and not in dev mode. For testing the build, you can use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **version 3**. Once you have built your site, run `wrangler pages dev .svelte-kit/cloudflare`. Ensure you have your [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) in your `wrangler.toml`. + +## Notes + +Functions contained in the `/functions` directory at the project's root will _not_ be included in the deployment, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/platform/functions/#advanced-mode). Functions should be implemented as [server endpoints](https://kit.svelte.dev/docs/routing#server) in your SvelteKit app. + +The `_headers` and `_redirects` files specific to Cloudflare Pages can be used for static asset responses (like images) by putting them into the `/static` folder. + +However, they will have no effect on responses dynamically rendered by SvelteKit, which should return custom headers or redirect responses from [server endpoints](https://kit.svelte.dev/docs/routing#server) or with the [`handle`](https://kit.svelte.dev/docs/hooks#server-hooks-handle) hook. + +## Troubleshooting + +### Further reading + +You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site). + +### Accessing the file system + +You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/70-adapter-cloudflare-workers.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/70-adapter-cloudflare-workers.md new file mode 100644 index 0000000000..e7781175ac --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/70-adapter-cloudflare-workers.md @@ -0,0 +1,123 @@ +--- +title: Cloudflare Workers +--- + +To deploy to [Cloudflare Workers](https://workers.cloudflare.com/), use [`adapter-cloudflare-workers`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare-workers). + +> Unless you have a specific reason to use `adapter-cloudflare-workers`, it's recommended that you use `adapter-cloudflare` instead. Both adapters have equivalent functionality, but Cloudflare Pages offers features like GitHub integration with automatic builds and deploys, preview deployments, instant rollback and so on. + +## Usage + +Install with `npm i -D @sveltejs/adapter-cloudflare-workers`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-cloudflare-workers'; + +export default { + kit: { + adapter: adapter() + } +}; +``` + +## Basic Configuration + +This adapter expects to find a [wrangler.toml](https://developers.cloudflare.com/workers/platform/sites/configuration) file in the project root. It should look something like this: + +```toml +/// file: wrangler.toml +name = "" +account_id = "" + +main = "./.cloudflare/worker.js" +site.bucket = "./.cloudflare/public" + +build.command = "npm run build" + +compatibility_date = "2021-11-12" +workers_dev = true +``` + +`` can be anything. `` can be found by logging into your [Cloudflare dashboard](https://dash.cloudflare.com) and grabbing it from the end of the URL: + +``` +https://dash.cloudflare.com/ +``` + +> You should add the `.cloudflare` directory (or whichever directories you specified for `main` and `site.bucket`) to your `.gitignore`. + +You will need to install [wrangler](https://developers.cloudflare.com/workers/wrangler/get-started/) and log in, if you haven't already: + +``` +npm i -g wrangler +wrangler login +``` + +Then, you can build your app and deploy it: + +```sh +wrangler deploy +``` + +## Custom config + +If you would like to use a config file other than `wrangler.toml`, you can do like so: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-cloudflare-workers'; + +export default { + kit: { + adapter: adapter({ config: '.toml' }) + } +}; +``` + +## Bindings + +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/platform/environment-variables/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with `context` and `caches`, meaning that you can access it in hooks and endpoints: + +```js +// @errors: 7031 +export async function POST({ request, platform }) { + const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); +} +``` + +> SvelteKit's built-in `$env` module should be preferred for environment variables. + +To make these types available to your app, reference them in your `src/app.d.ts`: + +```diff +/// file: src/app.d.ts +declare global { + namespace App { + interface Platform { ++ env?: { ++ YOUR_KV_NAMESPACE: KVNamespace; ++ YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; ++ }; + } + } +} + +export {}; +``` + +### Testing Locally + +`platform.env` is only available in the final build and not in dev mode. For testing the build, you can use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler). Once you have built your site, run `wrangler dev`. Ensure you have your [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) in your `wrangler.toml`. Wrangler version 3 is recommended. + +## Troubleshooting + +### Worker size limits + +When deploying to workers, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#how-do-i-use-x-with-sveltekit-how-do-i-use-a-client-side-only-library-that-depends-on-document-or-window) for more information. + +### Accessing the file system + +You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/80-adapter-netlify.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/80-adapter-netlify.md new file mode 100644 index 0000000000..168f1bdc07 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/80-adapter-netlify.md @@ -0,0 +1,114 @@ +--- +title: Netlify +--- + +To deploy to Netlify, use [`adapter-netlify`](https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify). + +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto), but adding it to your project allows you to specify Netlify-specific options. + +## Usage + +Install with `npm i -D @sveltejs/adapter-netlify`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-netlify'; + +export default { + kit: { + // default options are shown + adapter: adapter({ + // if true, will create a Netlify Edge Function rather + // than using standard Node-based functions + edge: false, + + // if true, will split your app into multiple functions + // instead of creating a single one for the entire app. + // if `edge` is true, this option cannot be used + split: false + }) + } +}; +``` + +Then, make sure you have a [netlify.toml](https://docs.netlify.com/configure-builds/file-based-configuration) file in the project root. This will determine where to write static assets based on the `build.publish` settings, as per this sample configuration: + +```toml +[build] + command = "npm run build" + publish = "build" +``` + +If the `netlify.toml` file or the `build.publish` value is missing, a default value of `"build"` will be used. Note that if you have set the publish directory in the Netlify UI to something else then you will need to set it in `netlify.toml` too, or use the default value of `"build"`. + +### Node version + +New projects will use Node 16 by default. However, if you're upgrading a project you created a while ago it may be stuck on an older version. See [the Netlify docs](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript) for details on manually specifying Node 16 or newer. + +## Netlify Edge Functions + +SvelteKit supports [Netlify Edge Functions](https://docs.netlify.com/netlify-labs/experimental-features/edge-functions/). If you pass the option `edge: true` to the `adapter` function, server-side rendering will happen in a Deno-based edge function that's deployed close to the site visitor. If set to `false` (the default), the site will deploy to Node-based Netlify Functions. + +```js +// @errors: 2307 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-netlify'; + +export default { + kit: { + adapter: adapter({ + // will create a Netlify Edge Function using Deno-based + // rather than using standard Node-based functions + edge: true + }) + } +}; +``` + +## Netlify alternatives to SvelteKit functionality + +You may build your app using functionality provided directly by SvelteKit without relying on any Netlify functionality. Using the SvelteKit versions of these features will allow them to be used in dev mode, tested with integration tests, and to work with other adapters should you ever decide to switch away from Netlify. However, in some scenarios you may find it beneficial to use the Netlify versions of these features. One example would be if you're migrating an app that's already hosted on Netlify to SvelteKit. + +### Redirect rules + +During compilation, redirect rules are automatically appended to your `_redirects` file. (If it doesn't exist yet, it will be created.) That means: + +- `[[redirects]]` in `netlify.toml` will never match as `_redirects` has a [higher priority](https://docs.netlify.com/routing/redirects/#rule-processing-order). So always put your rules in the [`_redirects` file](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file). +- `_redirects` shouldn't have any custom "catch all" rules such as `/* /foobar/:splat`. Otherwise the automatically appended rule will never be applied as Netlify is only processing [the first matching rule](https://docs.netlify.com/routing/redirects/#rule-processing-order). + +### Netlify Forms + +1. Create your Netlify HTML form as described [here](https://docs.netlify.com/forms/setup/#html-forms), e.g. as `/routes/contact/+page.svelte`. (Don't forget to add the hidden `form-name` input element!) +2. Netlify's build bot parses your HTML files at deploy time, which means your form must be [prerendered](https://kit.svelte.dev/docs/page-options#prerender) as HTML. You can either add `export const prerender = true` to your `contact.svelte` to prerender just that page or set the `kit.prerender.force: true` option to prerender all pages. +3. If your Netlify form has a [custom success message](https://docs.netlify.com/forms/setup/#success-messages) like `` then ensure the corresponding `/routes/success/+page.svelte` exists and is prerendered. + +### Netlify Functions + +With this adapter, SvelteKit endpoints are hosted as [Netlify Functions](https://docs.netlify.com/functions/overview/). Netlify function handlers have additional context, including [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) information. You can access this context via the `event.platform.context` field inside your hooks and `+page.server` or `+layout.server` endpoints. These are [serverless functions](https://docs.netlify.com/functions/overview/) when the `edge` property is `false` in the adapter config or [edge functions](https://docs.netlify.com/edge-functions/overview/#app) when it is `true`. + +```js +// @errors: 2705 7006 +/// file: +page.server.js +export const load = async (event) => { + const context = event.platform.context; + console.log(context); // shows up in your functions log in the Netlify app +}; +``` + +Additionally, you can add your own Netlify functions by creating a directory for them and adding the configuration to your `netlify.toml` file. For example: + +```toml +[build] + command = "npm run build" + publish = "build" + +[functions] + directory = "functions" +``` + +## Troubleshooting + +### Accessing the file system + +You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/90-adapter-vercel.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/90-adapter-vercel.md new file mode 100644 index 0000000000..6f72025b95 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/90-adapter-vercel.md @@ -0,0 +1,148 @@ +--- +title: Vercel +--- + +To deploy to Vercel, use [`adapter-vercel`](https://github.com/sveltejs/kit/tree/master/packages/adapter-vercel). + +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto), but adding it to your project allows you to specify Vercel-specific options. + +## Usage + +Install with `npm i -D @sveltejs/adapter-vercel`, then add the adapter to your `svelte.config.js`: + +```js +// @errors: 2307 2345 +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-vercel'; + +export default { + kit: { + adapter: adapter({ + // see the 'Deployment configuration' section below + }) + } +}; +``` + +## Deployment configuration + +To control how your routes are deployed to Vercel as functions, you can specify deployment configuration, either through the option shown above or with [`export const config`](page-options#config) inside `+server.js`, `+page(.server).js` and `+layout(.server).js` files. + +For example you could deploy some parts of your app as [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions)... + +```js +/// file: about/+page.js +/** @type {import('@sveltejs/adapter-vercel').Config} */ +export const config = { + runtime: 'edge' +}; +``` + +...and others as [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) (note that by specifying `config` inside a layout, it applies to all child pages): + +```js +/// file: admin/+layout.js +/** @type {import('@sveltejs/adapter-vercel').Config} */ +export const config = { + runtime: 'nodejs18.x' +}; +``` + +The following options apply to all functions: + +- `runtime`: `'edge'`, `'nodejs16.x'` or `'nodejs18.x'`. By default, the adapter will select `'nodejs16.x'` or `'nodejs18.x'` depending on the Node version your project is configured to use on the Vercel dashboard +- `regions`: an array of [edge network regions](https://vercel.com/docs/concepts/edge-network/regions) (defaulting to `["iad1"]` for serverless functions) or `'all'` if `runtime` is `edge` (its default). Note that multiple regions for serverless functions are only supported on Enterprise plans +- `split`: if `true`, causes a route to be deployed as an individual function. If `split` is set to `true` at the adapter level, all routes will be deployed as individual functions + +Additionally, the following option applies to edge functions: +- `external`: an array of dependencies that esbuild should treat as external when bundling functions. This should only be used to exclude optional dependencies that will not run outside Node + +And the following option apply to serverless functions: +- `memory`: the amount of memory available to the function. Defaults to `1024` Mb, and can be decreased to `128` Mb or [increased](https://vercel.com/docs/concepts/limits/overview#serverless-function-memory) in 64Mb increments up to `3008` Mb on Pro or Enterprise accounts +- `maxDuration`: maximum execution duration of the function. Defaults to `10` seconds for Hobby accounts, `15` for Pro and `900` for Enterprise +- `isr`: configuration Incremental Static Regeneration, described below + +If your functions need to access data in a specific region, it's recommended that they be deployed in the same region (or close to it) for optimal performance. + +## Incremental Static Regeneration + +Vercel supports [Incremental Static Regeneration](https://vercel.com/docs/concepts/incremental-static-regeneration/overview) (ISR), which provides the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content. + +To add ISR to a route, include the `isr` property in your `config` object: + +```js +/// file: blog/[slug]/+page.server.js +// @filename: ambient.d.ts +declare module '$env/static/private' { + export const BYPASS_TOKEN: string; +} + +// @filename: index.js +// ---cut--- +import { BYPASS_TOKEN } from '$env/static/private'; + +export const config = { + isr: { + // Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. + // Setting the value to `false` means it will never expire. + expiration: 60, + + // Random token that can be provided in the URL to bypass the cached version of the asset, by requesting the asset + // with a __prerender_bypass= cookie. + // + // Making a `GET` or `HEAD` request with `x-prerender-revalidate: ` will force the asset to be re-validated. + bypassToken: BYPASS_TOKEN, + + // List of valid query parameters. Other parameters (such as utm tracking codes) will be ignored, + // ensuring that they do not result in content being regenerated unnecessarily + allowQuery: ['search'] + } +}; +``` + +The `expiration` property is required; all others are optional. + +## Environment variables + +Vercel makes a set of [deployment-specific environment variables](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) available. Like other environment variables, these are accessible from `$env/static/private` and `$env/dynamic/private` (sometimes — more on that later), and inaccessible from their public counterparts. To access one of these variables from the client: + +```js +// @errors: 2305 +/// file: +layout.server.js +import { VERCEL_COMMIT_REF } from '$env/static/private'; + +/** @type {import('./$types').LayoutServerLoad} */ +export function load() { + return { + deploymentGitBranch: VERCEL_COMMIT_REF + }; +} +``` + +```svelte + + + +

This staging environment was deployed from {data.deploymentGitBranch}.

+``` + +Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend using `$env/static/private` — which will statically replace the variables, enabling optimisations like dead code elimination — rather than `$env/dynamic/private`. + +## Notes + +### Vercel functions + +If you have Vercel functions contained in the `api` directory at the project's root, any requests for `/api/*` will _not_ be handled by SvelteKit. You should implement these as [API routes](https://kit.svelte.dev/docs/routing#server) in your SvelteKit app instead, unless you need to use a non-JavaScript language in which case you will need to ensure that you don't have any `/api/*` routes in your SvelteKit app. + +### Node version + +Projects created before a certain date will default to using Node 14, while SvelteKit requires Node 16 or later. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version). + +## Troubleshooting + +### Accessing the file system + +You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/99-writing-adapters.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/99-writing-adapters.md new file mode 100644 index 0000000000..25b22f4037 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/99-writing-adapters.md @@ -0,0 +1,42 @@ +--- +title: Writing adapters +--- + +If an adapter for your preferred environment doesn't yet exist, you can build your own. We recommend [looking at the source for an adapter](https://github.com/sveltejs/kit/tree/master/packages) to a platform similar to yours and copying it as a starting point. + +Adapters packages must implement the following API, which creates an `Adapter`: + +```js +// @filename: ambient.d.ts +type AdapterSpecificOptions = any; + +// @filename: index.js +// ---cut--- +/** @param {AdapterSpecificOptions} options */ +export default function (options) { + /** @type {import('@sveltejs/kit').Adapter} */ + const adapter = { + name: 'adapter-package-name', + async adapt(builder) { + // adapter implementation + } + }; + + return adapter; +} +``` + +Within the `adapt` method, there are a number of things that an adapter should do: + +- Clear out the build directory +- Write SvelteKit output with `builder.writeClient`, `builder.writeServer`, and `builder.writePrerendered` +- Output code that: + - Imports `Server` from `${builder.getServerDirectory()}/index.js` + - Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })` + - Listens for requests from the platform, converts them to a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it + - expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond` + - Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/node/polyfills` helper for platforms that can use `undici` +- Bundle the output to avoid needing to install dependencies on the target platform, if necessary +- Put the user's static files and the generated JS/CSS in the correct location for the target platform + +Where possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `.svelte-kit/[adapter-name]`. diff --git a/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/index.md b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/index.md new file mode 100644 index 0000000000..1822eb2461 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/25-build-and-deploy/index.md @@ -0,0 +1,3 @@ +--- +title: "Build and deploy" +--- \ No newline at end of file diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/10-advanced-routing.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/10-advanced-routing.md new file mode 100644 index 0000000000..ca84a27df2 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/10-advanced-routing.md @@ -0,0 +1,287 @@ +--- +title: Advanced routing +--- + +## Rest parameters + +If the number of route segments is unknown, you can use rest syntax — for example you might implement GitHub's file viewer like so... + +```bash +/[org]/[repo]/tree/[branch]/[...file] +``` + +...in which case a request for `/sveltejs/kit/tree/master/documentation/docs/04-advanced-routing.md` would result in the following parameters being available to the page: + +```js +// @noErrors +{ + org: 'sveltejs', + repo: 'kit', + branch: 'master', + file: 'documentation/docs/04-advanced-routing.md' +} +``` + +> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching). + +### 404 pages + +Rest parameters also allow you to render custom 404s. Given these routes... + +``` +src/routes/ +├ marx-brothers/ +│ ├ chico/ +│ ├ harpo/ +│ ├ groucho/ +│ └ +error.svelte +└ +error.svelte +``` + +...the `marx-brothers/+error.svelte` file will _not_ be rendered if you visit `/marx-brothers/karl`, because no route was matched. If you want to render the nested error page, you should create a route that matches any `/marx-brothers/*` request, and return a 404 from it: + +```diff +src/routes/ +├ marx-brothers/ ++| ├ [...path]/ +│ ├ chico/ +│ ├ harpo/ +│ ├ groucho/ +│ └ +error.svelte +└ +error.svelte +``` + +```js +/// file: src/routes/marx-brothers/[...path]/+page.js +import { error } from '@sveltejs/kit'; + +/** @type {import('./$types').PageLoad} */ +export function load(event) { + throw error(404, 'Not Found'); +} +``` + +> If you don't handle 404 cases, they will appear in [`handleError`](hooks#shared-hooks-handleerror) + +## Optional parameters + +A route like `[lang]/home` contains a parameter named `lang` which is required. Sometimes it's beneficial to make these parameters optional, so that in this example both `home` and `en/home` point to the same page. You can do that by wrapping the parameter in another bracket pair: `[[lang]]/home` + +Note that an optional route parameter cannot follow a rest parameter (`[...rest]/[[optional]]`), since parameters are matched 'greedily' and the optional parameter would always be unused. + +## Matching + +A route like `src/routes/archive/[page]` would match `/archive/3`, but it would also match `/archive/potato`. We don't want that. You can ensure that route parameters are well-formed by adding a _matcher_ — which takes the parameter string (`"3"` or `"potato"`) and returns `true` if it is valid — to your [`params`](configuration#files) directory... + +```js +/// file: src/params/integer.js +/** @type {import('@sveltejs/kit').ParamMatcher} */ +export function match(param) { + return /^\d+$/.test(param); +} +``` + +...and augmenting your routes: + +```diff +-src/routes/archive/[page] ++src/routes/archive/[page=integer] +``` + +If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404. + +Each module in the `params` directory corresponds to a matcher, with the exception of `*.test.js` and `*.spec.js` files which may be used to unit test your matchers. + +> Matchers run both on the server and in the browser. + +## Sorting + +It's possible for multiple routes to match a given path. For example each of these routes would match `/foo-abc`: + +```bash +src/routes/[...catchall]/+page.svelte +src/routes/[[a=x]]/+page.svelte +src/routes/[b]/+page.svelte +src/routes/foo-[c]/+page.svelte +src/routes/foo-abc/+page.svelte +``` + +SvelteKit needs to know which route is being requested. To do so, it sorts them according to the following rules... + +- More specific routes are higher priority (e.g. a route with no parameters is more specific than a route with one dynamic parameter, and so on) +- Parameters with [matchers](#matching) (`[name=type]`) are higher priority than those without (`[name]`) +- `[[optional]]` and `[...rest]` parameters are ignored unless they are the final part of the route, in which case they are treated with lowest priority. In other words `x/[[y]]/z` is treated equivalently to `x/z` for the purposes of sorting +- Ties are resolved alphabetically + +...resulting in this ordering, meaning that `/foo-abc` will invoke `src/routes/foo-abc/+page.svelte`, and `/foo-def` will invoke `src/routes/foo-[c]/+page.svelte` rather than less specific routes: + +```bash +src/routes/foo-abc/+page.svelte +src/routes/foo-[c]/+page.svelte +src/routes/[[a=x]]/+page.svelte +src/routes/[b]/+page.svelte +src/routes/[...catchall]/+page.svelte +``` + +## Encoding + +Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` and `%` characters have special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route. + +To use these characters in your routes, you can use hexadecimal escape sequences, which have the format `[x+nn]` where `nn` is a hexadecimal character code: + +- `\` — `[x+5c]` +- `/` — `[x+2f]` +- `:` — `[x+3a]` +- `*` — `[x+2a]` +- `?` — `[x+3f]` +- `"` — `[x+22]` +- `<` — `[x+3c]` +- `>` — `[x+3e]` +- `|` — `[x+7c]` +- `#` — `[x+23]` +- `%` — `[x+25]` +- `[` — `[x+5b]` +- `]` — `[x+5d]` +- `(` — `[x+28]` +- `)` — `[x+29]` + +For example, to create a `/smileys/:-)` route, you would create a `src/routes/smileys/[x+3a]-[x+29]/+page.svelte` file. + +You can determine the hexadecimal code for a character with JavaScript: + +```js +':'.charCodeAt(0).toString(16); // '3a', hence '[x+3a]' +``` + +You can also use Unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't have a filename with an emoji in it, for example, then you can use the escaped characters. In other words, these are equivalent: + +``` +src/routes/[u+d83e][u+dd2a]/+page.svelte +src/routes/🤪/+page.svelte +``` + +The format for a Unicode escape sequence is `[u+nnnn]` where `nnnn` is a valid value between `0000` and `10ffff`. (Unlike JavaScript string escaping, there's no need to use surrogate pairs to represent code points above `ffff`.) To learn more about Unicode encodings, consult [Programming with Unicode](https://unicodebook.readthedocs.io/unicode_encodings.html). + +> Since TypeScript [struggles](https://github.com/microsoft/TypeScript/issues/13399) with directories with a leading `.` character, you may find it useful to encode these characters when creating e.g. [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) routes: `src/routes/[x+2e]well-known/...` + +## Advanced layouts + +By default, the _layout hierarchy_ mirrors the _route hierarchy_. In some cases, that might not be what you want. + +### (group) + +Perhaps you have some routes that are 'app' routes that should have one layout (e.g. `/dashboard` or `/item`), and others that are 'marketing' routes that should have a different layout (`/about` or `/testimonials`). We can group these routes with a directory whose name is wrapped in parentheses — unlike normal directories, `(app)` and `(marketing)` do not affect the URL pathname of the routes inside them: + +```diff +src/routes/ ++│ (app)/ +│ ├ dashboard/ +│ ├ item/ +│ └ +layout.svelte ++│ (marketing)/ +│ ├ about/ +│ ├ testimonials/ +│ └ +layout.svelte +├ admin/ +└ +layout.svelte +``` + +You can also put a `+page` directly inside a `(group)`, for example if `/` should be an `(app)` or a `(marketing)` page. + +### Breaking out of layouts + +The root layout applies to every page of your app — if omitted, it defaults to ``. If you want some pages to have a different layout hierarchy than the rest, then you can put your entire app inside one or more groups _except_ the routes that should not inherit the common layouts. + +In the example above, the `/admin` route does not inherit either the `(app)` or `(marketing)` layouts. + +### +page@ + +Pages can break out of the current layout hierarchy on a route-by-route basis. Suppose we have an `/item/[id]/embed` route inside the `(app)` group from the previous example: + +```diff +src/routes/ +├ (app)/ +│ ├ item/ +│ │ ├ [id]/ +│ │ │ ├ embed/ ++│ │ │ │ └ +page.svelte +│ │ │ └ +layout.svelte +│ │ └ +layout.svelte +│ └ +layout.svelte +└ +layout.svelte +``` + +Ordinarily, this would inherit the root layout, the `(app)` layout, the `item` layout and the `[id]` layout. We can reset to one of those layouts by appending `@` followed by the segment name — or, for the root layout, the empty string. In this example, we can choose from the following options: + +- `+page@[id].svelte` - inherits from `src/routes/(app)/item/[id]/+layout.svelte` +- `+page@item.svelte` - inherits from `src/routes/(app)/item/+layout.svelte` +- `+page@(app).svelte` - inherits from `src/routes/(app)/+layout.svelte` +- `+page@.svelte` - inherits from `src/routes/+layout.svelte` + +```diff +src/routes/ +├ (app)/ +│ ├ item/ +│ │ ├ [id]/ +│ │ │ ├ embed/ ++│ │ │ │ └ +page@(app).svelte +│ │ │ └ +layout.svelte +│ │ └ +layout.svelte +│ └ +layout.svelte +└ +layout.svelte +``` + +### +layout@ + +Like pages, layouts can _themselves_ break out of their parent layout hierarchy, using the same technique. For example, a `+layout@.svelte` component would reset the hierarchy for all its child routes. + +``` +src/routes/ +├ (app)/ +│ ├ item/ +│ │ ├ [id]/ +│ │ │ ├ embed/ +│ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte +│ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte +│ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte +│ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte +│ └ +layout.svelte +└ +layout.svelte +``` + +### When to use layout groups + +Not all use cases are suited for layout grouping, nor should you feel compelled to use them. It might be that your use case would result in complex `(group)` nesting, or that you don't want to introduce a `(group)` for a single outlier. It's perfectly fine to use other means such as composition (reusable `load` functions or Svelte components) or if-statements to achieve what you want. The following example shows a layout that rewinds to the root layout and reuses components and functions that other layouts can also use: + +```svelte + + + + + + +``` + +```js +/// file: src/routes/nested/route/+layout.js +// @filename: ambient.d.ts +declare module "$lib/reusable-load-function" { + export function reusableLoad(event: import('@sveltejs/kit').LoadEvent): Promise>; +} +// @filename: index.js +// ---cut--- +import { reusableLoad } from '$lib/reusable-load-function'; + +/** @type {import('./$types').PageLoad} */ +export function load(event) { + // Add additional logic here, if needed + return reusableLoad(event); +} +``` + +## Further reading + +- [Tutorial: Advanced Routing](https://learn.svelte.dev/tutorial/optional-params) diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/20-hooks.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/20-hooks.md new file mode 100644 index 0000000000..48a9a9388d --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/20-hooks.md @@ -0,0 +1,230 @@ +--- +title: Hooks +--- + +'Hooks' are app-wide functions you declare that SvelteKit will call in response to specific events, giving you fine-grained control over the framework's behaviour. + +There are two hooks files, both optional: + +- `src/hooks.server.js` — your app's server hooks +- `src/hooks.client.js` — your app's client hooks + +Code in these modules will run when the application starts up, making them useful for initializing database clients and so on. + +> You can configure the location of these files with [`config.kit.files.hooks`](configuration#files). + +## Server hooks + +The following hooks can be added to `src/hooks.server.js`: + +### handle + +This function runs every time the SvelteKit server receives a [request](web-standards#fetch-apis-request) — whether that happens while the app is running, or during [prerendering](page-options#prerender) — and determines the [response](web-standards#fetch-apis-response). It receives an `event` object representing the request and a function called `resolve`, which renders the route and generates a `Response`. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example). + +```js +/// file: src/hooks.server.js +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + if (event.url.pathname.startsWith('/custom')) { + return new Response('custom response'); + } + + const response = await resolve(event); + return response; +} +``` + +> Requests for static assets — which includes pages that were already prerendered — are _not_ handled by SvelteKit. + +If unimplemented, defaults to `({ event, resolve }) => resolve(event)`. To add custom data to the request, which is passed to handlers in `+server.js` and server `load` functions, populate the `event.locals` object, as shown below. + +```js +/// file: src/hooks.server.js +// @filename: ambient.d.ts +type User = { + name: string; +} + +declare namespace App { + interface Locals { + user: User; + } +} + +const getUserInformation: (cookie: string | void) => Promise; + +// @filename: index.js +// ---cut--- +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + event.locals.user = await getUserInformation(event.cookies.get('sessionid')); + + const response = await resolve(event); + response.headers.set('x-custom-header', 'potato'); + + return response; +} +``` + +You can define multiple `handle` functions and execute them with [the `sequence` helper function](modules#sveltejs-kit-hooks). + +`resolve` also supports a second, optional parameter that gives you more control over how the response will be rendered. That parameter is an object that can have the following fields: + +- `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise` — applies custom transforms to HTML. If `done` is true, it's the final chunk. Chunks are not guaranteed to be well-formed HTML (they could include an element's opening tag but not its closing tag, for example) but they will always be split at sensible boundaries such as `%sveltekit.head%` or layout/page components. +- `filterSerializedResponseHeaders(name: string, value: string): boolean` — determines which headers should be included in serialized responses when a `load` function loads a resource with `fetch`. By default, none will be included. +- `preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean` — determines what files should be added to the `` tag to preload it. The method is called with each file that was found at build time while constructing the code chunks — so if you for example have `import './styles.css` in your `+page.svelte`, `preload` will be called with the resolved path to that CSS file when visiting that page. Note that in dev mode `preload` is _not_ called, since it depends on analysis that happens at build time. Preloading can improve performance by downloading assets sooner, but it can also hurt if too much is downloaded unnecessarily. By default, `js` and `css` files will be preloaded. `asset` files are not preloaded at all currently, but we may add this later after evaluating feedback. + +```js +/// file: src/hooks.server.js +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + const response = await resolve(event, { + transformPageChunk: ({ html }) => html.replace('old', 'new'), + filterSerializedResponseHeaders: (name) => name.startsWith('x-'), + preload: ({ type, path }) => type === 'js' || path.includes('/important/') + }); + + return response; +} +``` + +Note that `resolve(...)` will never throw an error, it will always return a `Promise` with the appropriate status code. If an error is thrown elsewhere during `handle`, it is treated as fatal, and SvelteKit will respond with a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. You can read more about error handling [here](errors). + +### handleFetch + +This function allows you to modify (or replace) a `fetch` request that happens inside a `load` or `action` function that runs on the server (or during pre-rendering). + +For example, your `load` function might make a request to a public URL like `https://api.yourapp.com` when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet). + +```js +/// file: src/hooks.server.js +/** @type {import('@sveltejs/kit').HandleFetch} */ +export async function handleFetch({ request, fetch }) { + if (request.url.startsWith('https://api.yourapp.com/')) { + // clone the original request, but change the URL + request = new Request( + request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'), + request + ); + } + + return fetch(request); +} +``` + +**Credentials** + +For same-origin requests, SvelteKit's `fetch` implementation will forward `cookie` and `authorization` headers unless the `credentials` option is set to `"omit"`. + +For cross-origin requests, `cookie` will be included if the request URL belongs to a subdomain of the app — for example if your app is on `my-domain.com`, and your API is on `api.my-domain.com`, cookies will be included in the request. + +If your app and your API are on sibling subdomains — `www.my-domain.com` and `api.my-domain.com` for example — then a cookie belonging to a common parent domain like `my-domain.com` will _not_ be included, because SvelteKit has no way to know which domain the cookie belongs to. In these cases you will need to manually include the cookie using `handleFetch`: + +```js +/// file: src/hooks.server.js +// @errors: 2345 +/** @type {import('@sveltejs/kit').HandleFetch} */ +export async function handleFetch({ event, request, fetch }) { + if (request.url.startsWith('https://api.my-domain.com/')) { + request.headers.set('cookie', event.request.headers.get('cookie')); + } + + return fetch(request); +} +``` + +## Shared hooks + +The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`: + +### handleError + +If an unexpected error is thrown during loading or rendering, this function will be called with the `error` and the `event`. This allows for two things: + +- you can log the error +- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `$page.error`. It defaults to `{ message: 'Not Found' }` in case of a 404 (you can detect them through `event.route.id` being `null`) and to `{ message: 'Internal Error' }` for everything else. To make this type-safe, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). + +The following code shows an example of typing the error shape as `{ message: string; errorId: string }` and returning it accordingly from the `handleError` functions: + +```ts +/// file: src/app.d.ts +declare global { + namespace App { + interface Error { + message: string; + errorId: string; + } + } +} + +export {}; +``` + +```js +/// file: src/hooks.server.js +// @errors: 2322 +// @filename: ambient.d.ts +declare module '@sentry/sveltekit' { + export const init: (opts: any) => void; + export const captureException: (error: any, opts: any) => void; +} + +// @filename: index.js +// ---cut--- +import * as Sentry from '@sentry/sveltekit'; +import crypto from 'crypto'; + +Sentry.init({/*...*/}) + +/** @type {import('@sveltejs/kit').HandleServerError} */ +export async function handleError({ error, event }) { + const errorId = crypto.randomUUID(); + // example integration with https://sentry.io/ + Sentry.captureException(error, { extra: { event, errorId } }); + + return { + message: 'Whoops!', + errorId + }; +} +``` + +```js +/// file: src/hooks.client.js +// @errors: 2322 +// @filename: ambient.d.ts +declare module '@sentry/sveltekit' { + export const init: (opts: any) => void; + export const captureException: (error: any, opts: any) => void; +} + +// @filename: index.js +// ---cut--- +import * as Sentry from '@sentry/sveltekit'; + +Sentry.init({/*...*/}) + +/** @type {import('@sveltejs/kit').HandleClientError} */ +export async function handleError({ error, event }) { + const errorId = crypto.randomUUID(); + // example integration with https://sentry.io/ + Sentry.captureException(error, { extra: { event, errorId } }); + + return { + message: 'Whoops!', + errorId + }; +} +``` + +> In `src/hooks.client.js`, the type of `handleError` is `HandleClientError` instead of `HandleServerError`, and `event` is a `NavigationEvent` rather than a `RequestEvent`. + +This function is not called for _expected_ errors (those thrown with the [`error`](modules#sveltejs-kit-error) function imported from `@sveltejs/kit`). + +During development, if an error occurs because of a syntax error in your Svelte code, the passed in error has a `frame` property appended highlighting the location of the error. + +> Make sure that `handleError` _never_ throws an error + +## Further reading + +- [Tutorial: Hooks](https://learn.svelte.dev/tutorial/handle) diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/25-errors.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/25-errors.md new file mode 100644 index 0000000000..4918db08b6 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/25-errors.md @@ -0,0 +1,161 @@ +--- +title: Errors +--- + +Errors are an inevitable fact of software development. SvelteKit handles errors differently depending on where they occur, what kind of errors they are, and the nature of the incoming request. + +## Error objects + +SvelteKit distinguishes between expected and unexpected errors, both of which are represented as simple `{ message: string }` objects by default. + +You can add additional properties, like a `code` or a tracking `id`, as shown in the examples below. (When using TypeScript this requires you to redefine the `Error` type as described in [type safety](errors#type-safety)). + +## Expected errors + +An _expected_ error is one created with the [`error`](modules#sveltejs-kit-error) helper imported from `@sveltejs/kit`: + +```js +/// file: src/routes/blog/[slug]/+page.server.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function getPost(slug: string): Promise<{ title: string, content: string } | undefined> +} + +// @filename: index.js +// ---cut--- +import { error } from '@sveltejs/kit'; +import * as db from '$lib/server/database'; + +/** @type {import('./$types').PageServerLoad} */ +export async function load({ params }) { + const post = await db.getPost(params.slug); + + if (!post) { + throw error(404, { + message: 'Not found' + }); + } + + return { post }; +} +``` + +This tells SvelteKit to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where `$page.error` is the object provided as the second argument to `error(...)`. + +```svelte + + + +

{$page.error.message}

+``` + +You can add extra properties to the error object if needed... + +```diff +throw error(404, { + message: 'Not found', ++ code: 'NOT_FOUND' +}); +``` + +...otherwise, for convenience, you can pass a string as the second argument: + +```diff +-throw error(404, { message: 'Not found' }); ++throw error(404, 'Not found'); +``` + +## Unexpected errors + +An _unexpected_ error is any other exception that occurs while handling a request. Since these can contain sensitive information, unexpected error messages and stack traces are not exposed to users. + +By default, unexpected errors are printed to the console (or, in production, your server logs), while the error that is exposed to the user has a generic shape: + +```json +{ "message": "Internal Error" } +``` + +Unexpected errors will go through the [`handleError`](hooks#shared-hooks-handleerror) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object. + +```js +/// file: src/hooks.server.js +// @errors: 2322 1360 2571 2339 +// @filename: ambient.d.ts +declare module '@sentry/sveltekit' { + export const init: (opts: any) => void; + export const captureException: (error: any, opts: any) => void; +} + +// @filename: index.js +// ---cut--- +import * as Sentry from '@sentry/sveltekit'; + +Sentry.init({/*...*/}) + +/** @type {import('@sveltejs/kit').HandleServerError} */ +export function handleError({ error, event }) { + // example integration with https://sentry.io/ + Sentry.captureException(error, { extra: { event } }); + + return { + message: 'Whoops!', + code: error?.code ?? 'UNKNOWN' + }; +} +``` + +> Make sure that `handleError` _never_ throws an error + +## Responses + +If an error occurs inside `handle` or inside a [`+server.js`](routing#server) request handler, SvelteKit will respond with either a fallback error page or a JSON representation of the error object, depending on the request's `Accept` headers. + +You can customise the fallback error page by adding a `src/error.html` file: + +```html + + + + + %sveltekit.error.message% + + +

My custom error page

+

Status: %sveltekit.status%

+

Message: %sveltekit.error.message%

+ + +``` + +SvelteKit will replace `%sveltekit.status%` and `%sveltekit.error.message%` with their corresponding values. + +If the error instead occurs inside a `load` function while rendering a page, SvelteKit will render the [`+error.svelte`](routing#error) component nearest to where the error occurred. If the error occurs inside a `load` function in `+layout(.server).js`, the closest error boundary in the tree is an `+error.svelte` file _above_ that layout (not next to it). + +The exception is when the error occurs inside the root `+layout.js` or `+layout.server.js`, since the root layout would ordinarily _contain_ the `+error.svelte` component. In this case, SvelteKit uses the fallback error page. + +## Type safety + +If you're using TypeScript and need to customize the shape of errors, you can do so by declaring an `App.Error` interface in your app (by convention, in `src/app.d.ts`, though it can live anywhere that TypeScript can 'see'): + +```diff +/// file: src/app.d.ts +declare global { + namespace App { + interface Error { ++ code: string; ++ id: string; + } + } +} + +export {}; +``` + +This interface always includes a `message: string` property. + +## Further reading + +- [Tutorial: Errors and redirects](https://learn.svelte.dev/tutorial/error-basics) +- [Tutorial: Hooks](https://learn.svelte.dev/tutorial/handle) diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/30-link-options.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/30-link-options.md new file mode 100644 index 0000000000..4d2445a59f --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/30-link-options.md @@ -0,0 +1,129 @@ +--- +title: Link options +--- + +In SvelteKit, `
` elements (rather than framework-specific `` components) are used to navigate between the routes of your app. If the user clicks on a link whose `href` is 'owned' by the app (as opposed to, say, a link to an external site) then SvelteKit will navigate to the new page by importing its code and then calling any `load` functions it needs to fetch data. + +You can customise the behaviour of links with `data-sveltekit-*` attributes. These can be applied to the `` itself, or to a parent element. + +These options also apply to `` elements with [`method="GET"`](form-actions#get-vs-post). + +## data-sveltekit-preload-data + +Before the browser registers that the user has clicked on a link, we can detect that they've hovered the mouse over it (on desktop) or that a `touchstart` or `mousedown` event was triggered. In both cases, we can make an educated guess that a `click` event is coming. + +SvelteKit can use this information to get a head start on importing the code and fetching the page's data, which can give us an extra couple of hundred milliseconds — the difference between a user interface that feels laggy and one that feels snappy. + +We can control this behaviour with the `data-sveltekit-preload-data` attribute, which can have one of two values: + +- `"hover"` means that preloading will start if the mouse comes to a rest over a link. On mobile, preloading begins on `touchstart` +- `"tap"` means that preloading will start as soon as a `touchstart` or `mousedown` event is registered + +The default project template has a `data-sveltekit-preload-data="hover"` attribute applied to the `` element in `src/app.html`, meaning that every link is preloaded on hover by default: + +```html + +
%sveltekit.body%
+ +``` + +Sometimes, calling `load` when the user hovers over a link might be undesirable, either because it's likely to result in false positives (a click needn't follow a hover) or because data is updating very quickly and a delay could mean staleness. + +In these cases, you can specify the `"tap"` value, which causes SvelteKit to call `load` only when the user taps or clicks on a link: + +```html +
+ Get current stonk values + +``` + +> You can also programmatically invoke `preloadData` from `$app/navigation`. + +Data will never be preloaded if the user has chosen reduced data usage, meaning [`navigator.connection.saveData`](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData) is `true`. + +## data-sveltekit-preload-code + +Even in cases where you don't want to preload _data_ for a link, it can be beneficial to preload the _code_. The `data-sveltekit-preload-code` attribute works similarly to `data-sveltekit-preload-data`, except that it can take one of four values, in decreasing 'eagerness': + +- `"eager"` means that links will be preloaded straight away +- `"viewport"` means that links will be preloaded once they enter the viewport +- `"hover"` - as above, except that only code is preloaded +- `"tap"` - as above, except that only code is preloaded + +Note that `viewport` and `eager` only apply to links that are present in the DOM immediately following navigation — if a link is added later (in an `{#if ...}` block, for example) it will not be preloaded until triggered by `hover` or `tap`. This is to avoid performance pitfalls resulting from aggressively observing the DOM for changes. + +> Since preloading code is a prerequisite for preloading data, this attribute will only have an effect if it specifies a more eager value than any `data-sveltekit-preload-data` attribute that is present. + +As with `data-sveltekit-preload-data`, this attribute will be ignored if the user has chosen reduced data usage. + +## data-sveltekit-reload + +Occasionally, we need to tell SvelteKit not to handle a link, but allow the browser to handle it. Adding a `data-sveltekit-reload` attribute to a link... + +```html +Path +``` + +...will cause a full-page navigation when the link is clicked. + +Links with a `rel="external"` attribute will receive the same treatment. In addition, they will be ignored during [prerendering](page-options#prerender). + +## data-sveltekit-replacestate + +Sometimes you don't want navigation to create a new entry in the browser's session history. Adding a `data-sveltekit-replacestate` attribute to a link... + +```html +Path +``` + +...will replace the current `history` entry rather than creating a new one with `pushState` when the link is clicked. + +## data-sveltekit-keepfocus + +Sometimes you don't want [focus to be reset](accessibility#focus-management) after navigation. For example, maybe you have a search form that submits as the user is typing, and you want to keep focus on the text input. Adding a `data-sveltekit-keepfocus` attribute to it... + +```html + + +
+``` + +...will cause the currently focused element to retain focus after navigation. In general, avoid using this attribute on links, since the focused element would be the `` tag (and not a previously focused element) and screen reader and other assistive technology users often expect focus to be moved after a navigation. You should also only use this attribute on elements that still exist after navigation. If the element no longer exists, the user's focus will be lost, making for a confusing experience for assistive technology users. + +## data-sveltekit-noscroll + +When navigating to internal links, SvelteKit mirrors the browser's default navigation behaviour: it will change the scroll position to 0,0 so that the user is at the very top left of the page (unless the link includes a `#hash`, in which case it will scroll to the element with a matching ID). + +In certain cases, you may wish to disable this behaviour. Adding a `data-sveltekit-noscroll` attribute to a link... + +```html +Path +``` + +...will prevent scrolling after the link is clicked. + +## Disabling options + +To disable any of these options inside an element where they have been enabled, use the `"false"` value: + +```html +
+ + a + b + c + +
+ + d + e + f +
+
+``` + +To apply an attribute to an element conditionally, do this: + +```svelte +
+``` \ No newline at end of file diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/40-service-workers.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/40-service-workers.md new file mode 100644 index 0000000000..a4888a6ac8 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/40-service-workers.md @@ -0,0 +1,150 @@ +--- +title: Service workers +--- + +Service workers act as proxy servers that handle network requests inside your app. This makes it possible to make your app work offline, but even if you don't need offline support (or can't realistically implement it because of the type of app you're building), it's often worth using service workers to speed up navigation by precaching your built JS and CSS. + +In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker/index.js`) it will be bundled and automatically registered. You can change the [location of your service worker](configuration#files) if you need to. + +You can [disable automatic registration](configuration#serviceworker) if you need to register the service worker with your own logic or use another solution. The default registration looks something like this: + +```js +if ('serviceWorker' in navigator) { + addEventListener('load', function () { + navigator.serviceWorker.register('./path/to/service-worker.js'); + }); +} +``` + +## Inside the service worker + +Inside the service worker you have access to the [`$service-worker` module](modules#$service-worker), which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string, which you can use for creating a unique cache name, and the deployment's `base` path. If your Vite config specifies `define` (used for global variable replacements), this will be applied to service workers as well as your server/client builds. + +The following example caches the built app and any files in `static` eagerly, and caches all other requests as they happen. This would make each page work offline once visited. + +```js +// @errors: 2339 +/// +import { build, files, version } from '$service-worker'; + +// Create a unique cache name for this deployment +const CACHE = `cache-${version}`; + +const ASSETS = [ + ...build, // the app itself + ...files // everything in `static` +]; + +self.addEventListener('install', (event) => { + // Create a new cache and add all files to it + async function addFilesToCache() { + const cache = await caches.open(CACHE); + await cache.addAll(ASSETS); + } + + event.waitUntil(addFilesToCache()); +}); + +self.addEventListener('activate', (event) => { + // Remove previous cached data from disk + async function deleteOldCaches() { + for (const key of await caches.keys()) { + if (key !== CACHE) await caches.delete(key); + } + } + + event.waitUntil(deleteOldCaches()); +}); + +self.addEventListener('fetch', (event) => { + // ignore POST requests etc + if (event.request.method !== 'GET') return; + + async function respond() { + const url = new URL(event.request.url); + const cache = await caches.open(CACHE); + + // `build`/`files` can always be served from the cache + if (ASSETS.includes(url.pathname)) { + const response = await cache.match(url.pathname); + + if (response) { + return response; + } + } + + // for everything else, try the network first, but + // fall back to the cache if we're offline + try { + const response = await fetch(event.request); + + // if we're offline, fetch can return a value that is not a Response + // instead of throwing - and we can't pass this non-Response to respondWith + if (!(response instanceof Response)) { + throw new Error('invalid response from fetch'); + } + + if (response.status === 200) { + cache.put(event.request, response.clone()); + } + + return response; + } catch (err) { + const response = await cache.match(event.request); + + if (response) { + return response; + } + + // if there's no cache, then just error out + // as there is nothing we can do to respond to this request + throw err; + } + } + + event.respondWith(respond()); +}); +``` + +> Be careful when caching! In some cases, stale data might be worse than data that's unavailable while offline. Since browsers will empty caches if they get too full, you should also be careful about caching large assets like video files. + +## During development + +The service worker is bundled for production, but not during development. For that reason, only browsers that support [modules in service workers](https://web.dev/es-modules-in-sw) will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the `{ type: 'module' }` option in development: + +```js +import { dev } from '$app/environment'; + +navigator.serviceWorker.register('/service-worker.js', { + type: dev ? 'module' : 'classic' +}); +``` + +> `build` and `prerendered` are empty arrays during development + +## Type safety + +Setting up proper types for service workers requires some manual setup. Inside your `service-worker.js`, add the following to the top of your file: + +```original-js +/// +/// +/// +/// + +const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); +``` +```generated-ts +/// +/// +/// +/// + +const sw = self as unknown as ServiceWorkerGlobalScope; +``` + +This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file. The reference to the SvelteKit types ensures that the `$service-worker` import has proper type definitions. + +## Other solutions + +SvelteKit's service worker implementation is deliberately low-level. If you need a more full-flegded but also more opinionated solution, we recommend looking at solutions like [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html), which uses [Workbox](https://web.dev/learn/pwa/workbox). For more general information on service workers, we recommend [the MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/50-server-only-modules.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/50-server-only-modules.md new file mode 100644 index 0000000000..78f3a75da2 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/50-server-only-modules.md @@ -0,0 +1,60 @@ +--- +title: Server-only modules +--- + +Like a good friend, SvelteKit keeps your secrets. When writing your backend and frontend in the same repository, it can be easy to accidentally import sensitive data into your front-end code (environment variables containing API keys, for example). SvelteKit provides a way to prevent this entirely: server-only modules. + +## Private environment variables + +The `$env/static/private` and `$env/dynamic/private` modules, which are covered in the [modules](modules) section, can only be imported into modules that only run on the server, such as [`hooks.server.js`](hooks#server-hooks) or [`+page.server.js`](routing#page-page-server-js). + +## Your modules + +You can make your own modules server-only in two ways: + +- adding `.server` to the filename, e.g. `secrets.server.js` +- placing them in `$lib/server`, e.g. `$lib/server/secrets.js` + +## How it works + +Any time you have public-facing code that imports server-only code (whether directly or indirectly)... + +```js +// @errors: 7005 +/// file: $lib/server/secrets.js +export const atlantisCoordinates = [/* redacted */]; +``` + +```js +// @errors: 2307 7006 7005 +/// file: src/routes/utils.js +export { atlantisCoordinates } from '$lib/server/secrets.js'; + +export const add = (a, b) => a + b; +``` + +```html +/// file: src/routes/+page.svelte + +``` + +...SvelteKit will error: + +``` +Cannot import $lib/server/secrets.js into public-facing code: +- src/routes/+page.svelte + - src/routes/utils.js + - $lib/server/secrets.js +``` + +Even though the public-facing code — `src/routes/+page.svelte` — only uses the `add` export and not the secret `atlantisCoordinates` export, the secret code could end up in JavaScript that the browser downloads, and so the import chain is considered unsafe. + +This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``, with one small caveat: during development, if there are two or more dynamic imports between the public-facing code and the server-only module, the illegal import will not be detected the first time the code is loaded. + +> Unit testing frameworks like Vitest do not distinguish between server-only and public-facing code. For this reason, illegal import detection is disabled when running tests, as determined by `process.env.TEST === 'true'`. + +## Further reading + +- [Tutorial: Environment variables](https://learn.svelte.dev/tutorial/env-static-private) diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/60-images.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/60-images.md new file mode 100644 index 0000000000..6471eb68b4 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/60-images.md @@ -0,0 +1,152 @@ +--- +title: Images +--- + +Images can have a big impact on your app's performance. For best results, you should optimize them by doing the following: + +- generate optimal formats like `.avif` and `.webp` +- create different sizes for different screens +- ensure that assets can be cached effectively + +Doing this manually is tedious. There are a variety of techniques you can use, depending on your needs and preferences. + +## Vite's built-in handling + +[Vite will automatically process imported assets](https://vitejs.dev/guide/assets.html) for improved performance. This includes assets referenced via the CSS `url()` function. Hashes will be added to the filenames so that they can be cached, and assets smaller than `assetsInlineLimit` will be inlined. Vite's asset handling is most often used for images, but is also useful for video, audio, etc. + +```svelte + + +The project logo +``` + +## @sveltejs/enhanced-img + +> **WARNING**: The `@sveltejs/enhanced-img` package is experimental. It uses pre-1.0 versioning and may introduce breaking changes with every minor version release. + +`@sveltejs/enhanced-img` builds on top of Vite's built-in asset handling. It offers plug and play image processing that serves smaller file formats like `avif` or `webp`, automatically sets the intrinsic `width` and `height` of the image to avoid layout shift, creates images of multiple sizes for various devices, and strips EXIF data for privacy. It will work in any Vite-based project including, but not limited to, SvelteKit projects. + +### Setup + +Install: + +```bash +npm install --save-dev @sveltejs/enhanced-img +``` + +Adjust `vite.config.js`: + +```diff +import { sveltekit } from '@sveltejs/kit/vite'; ++import { enhancedImages } from '@sveltejs/enhanced-img'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ ++ enhancedImages(), + sveltekit() + ] +}); +``` + +### Basic usage + +Use in your `.svelte` components by using `` rather than `` and referencing the image file with a [Vite asset import](https://vitejs.dev/guide/assets.html#static-asset-handling) path: + +```svelte + +``` + +At build time, your `` tag will be replaced with an `` wrapped by a `` providing multiple image types and sizes. It's only possible to downscale images without losing quality, which means that you should provide the highest resolution image that you need — smaller versions will be generated for the various device types that may request an image. + +You should provide your image at 2x resolution for HiDPI displays (a.k.a. retina displays). `` will automatically take care of serving smaller versions to smaller devices. + +If you wish to add styles to your ``, you should add a `class` and target that. + +### Dynamically choosing an image + +You can also manually import an image asset and pass it to an ``. This is useful when you have a collection of static images and would like to dynamically choose one or [iterate over them](https://github.com/sveltejs/kit/blob/master/sites/kit.svelte.dev/src/routes/home/Showcase.svelte). In this case you will need to update both the `import` statement and `` element as shown below to indicate you'd like process them. + +```svelte + + + +``` + +You can also use [Vite's `import.meta.glob`](https://vitejs.dev/guide/features.html#glob-import). Note that you will have to specify `enhanced` via a [custom query](https://vitejs.dev/guide/features.html#custom-queries): + +```js +const pictures = import.meta.glob( + '/path/to/assets/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}', + { + query: { + enhanced: true + } + } +); +``` + +### Intrinsic Dimensions + +`width` and `height` are optional as they can be inferred from the source image and will be automatically added when the `` tag is preprocessed. With these attributes, the browser can reserve the correct amount of space, preventing [layout shift](https://web.dev/articles/cls). If you'd like to use a different `width` and `height` you can style the image with CSS. Because the preprocessor adds a `width` and `height` for you, if you'd like one of the dimensions to be automatically calculated then you will need to specify that: + +```svelte + +``` + +### `srcset` and `sizes` + +If you have a large image, such as a hero image taking the width of the design, you should specify `sizes` so that smaller versions are requested on smaller devices. E.g. if you have a 1280px image you may want to specify something like: + +```svelte + +``` + +If `sizes` is specified, `` will generate small images for smaller devices and populate the `srcset` attribute. + +The smallest picture generated automatically will have a width of 540px. If you'd like smaller images or would otherwise like to specify custom widths, you can do that with the `w` query parameter: +```svelte + +``` + +If `sizes` is not provided, then a HiDPI/Retina image and a standard resolution image will be generated. The image you provide should be 2x the resolution you wish to display so that the browser can display that image on devices with a high [device pixel ratio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). + +### Per-image transforms + +By default, enhanced images will be transformed to more efficient formats. However, you may wish to apply other transforms such as a blur, quality, flatten, or rotate operation. You can run per-image transforms by appending a query string: + +```svelte + +``` + +[See the imagetools repo for the full list of directives](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md). + +## Loading images dynamically from a CDN + +In some cases, the images may not be accessible at build time — e.g. they may live inside a content management system or elsewhere. + +Using a content delivery network (CDN) can allow you to optimize these images dynamically, and provides more flexibility with regards to sizes, but it may involve some setup overhead and usage costs. Depending on caching strategy, the browser may not be able to use a cached copy of the asset until a [304 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) is received from the CDN. Building HTML to target CDNs may result in slightly smaller and simpler HTML because they can serve the appropriate file format for an `` tag based on the `User-Agent` header whereas build-time optimizations must produce `` tags with multiple sources. Finally, some CDNs may generate images lazily, which could have a negative performance impact for sites with low traffic and frequently changing images. + +CDNs can generally be used without any need for a library. However, there are a number of libraries with Svelte support that make it easier. [`@unpic/svelte`](https://unpic.pics/img/svelte/) is a CDN-agnostic library with support for a large number of providers. You may also find that specific CDNs like [Cloudinary](https://svelte.cloudinary.dev/) have Svelte support. Finally, some content management systems (CMS) which support Svelte (such as [Contentful](https://www.contentful.com/sveltekit-starter-guide/), [Storyblok](https://github.com/storyblok/storyblok-svelte), and [Contentstack](https://www.contentstack.com/docs/developers/sample-apps/build-a-starter-website-with-sveltekit-and-contentstack)) have built-in support for image handling. + +## Best practices + +- For each image type, use the appropriate solution from those discussed above. You can mix and match all three solutions in one project. For example, you may use Vite's built-in handling to provide images for `` tags, display images on your homepage with `@sveltejs/enhanced-img`, and display user-submitted content with a dynamic approach. +- Consider serving all images via CDN regardless of the image optimization types you use. CDNs reduce latency by distributing copies of static assets globally. +- Your original images should have a good quality/resolution and should have 2x the width it will be displayed at to serve HiDPI devices. Image processing can size images down to save bandwidth when serving smaller screens, but it would be a waste of bandwidth to invent pixels to size images up. +- For images which are much larger than the width of a mobile device (roughly 400px), such as a hero image taking the width of the page design, specify `sizes` so that smaller images can be served on smaller devices. +- Choose one image per page which is the most important/largest one and give it `priority` so it loads faster. This gives you better web vitals scores (largest contentful paint in particular). +- Give the image a container or styling so that it is constrained and does not jump around. `width` and `height` help the browser reserving space while the image is still loading. `@sveltejs/enhanced-img` will add a `width` and `height` for you. +- Always provide a good `alt` text. The Svelte compiler will warn you if you don't do this. diff --git a/apps/svelte.dev/content/docs/kit/v01/30-advanced/65-snapshots.md b/apps/svelte.dev/content/docs/kit/v01/30-advanced/65-snapshots.md new file mode 100644 index 0000000000..2ea91be8b3 --- /dev/null +++ b/apps/svelte.dev/content/docs/kit/v01/30-advanced/65-snapshots.md @@ -0,0 +1,34 @@ +--- +title: Snapshots +--- + +Ephemeral DOM state — like scroll positions on sidebars, the content of `` elements and so on — is discarded when you navigate from one page to another. + +For example, if the user fills out a form but clicks a link before submitting, then hits the browser's back button, the values they filled in will be lost. In cases where it's valuable to preserve that input, you can take a _snapshot_ of DOM state, which can then be restored if the user navigates back. + +To do this, export a `snapshot` object with `capture` and `restore` methods from a `+page.svelte` or `+layout.svelte`: + +```svelte + + + +
+ +