Conceitos Principais
Carregando Dados
Editar esta página na GitHubAntes dum componente +page.svelte
(e os seus componentes que contém +layout.svelte
) poderem ser interpretados, frequentemente precisamos receber alguns dados. Isto é feito definindo as funções load
.
Dados da Páginapermalink
Um ficheiro +page.svelte
pode ter um +page.js
irmão que exporta uma função load
, o valor de retorno do que está disponível a uma página através da propriedade data
:
ts
/** @type {import('./$types').PageLoad} */export functionload ({params }) {return {post : {title : `Title for ${params .slug } goes here`,content : `Content for ${params .slug } goes here`}};}
ts
import type {PageLoad } from './$types';export constload :PageLoad = ({params }) => {return {post : {title : `Title for ${params .slug } goes here`,content : `Content for ${params .slug } goes here`,},};};
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
Graças ao módulo $types
gerado, obtemos a segurança de tipo completa.
Uma função load
num ficheiro +page
executa ambos no servidor e no navegador (a menos que combinada com export const ssr = false
, no qual caso esta apenas executará no navegador). Se a nossa função load
deveria sempre executar no servidor (porque esta usa variáveis de ambiente privadas, por exemplo, ou acessa uma base de dados) então esta iria num +page.server.js
.
Uma versão mais realística da nossa função load
da publicação de blogue, que apenas executa no servidor e puxa dados a partir duma base de dados, seria parecida com isto:
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
ts
import * asdb from '$lib/server/database';import type {PageServerLoad } from './$types';export constload :PageServerLoad = async ({params }) => {return {post : awaitdb .getPost (params .slug ),};};
Repara que o tipo mudou de PageLoad
à PageServerLoad
, porque as funções load
do servidor podem acessar argumentos adicionais. Para entender quando usar +page.js
e quando usar +page.server.js
, consultar Universal vs servidor.
Dados da Disposiçãopermalink
Nossos ficheiros +layout.svelte
também podem carregar dados, através de +layout.js
ou +layout.server.js
:
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
ts
import * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload :LayoutServerLoad = async () => {return {posts : awaitdb .getPostSummaries (),};};
<script>
/** @type {import('./$types').LayoutData} */
export let data;
</script>
<main>
<!-- +page.svelte é interpretado neste <slot> -->
<slot />
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside>
<script lang="ts">
import type { LayoutData } from './$types';
export let data: LayoutData;
</script>
<main>
<!-- +page.svelte é interpretado neste <slot> -->
<slot />
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside>
Os dados retornados a partir das funções load
da disposição estão disponíveis aos componentes filhos de +layout.svelte
e o componente +page.svelte
bem como a disposição que a qual esta 'pertence':
<script>
import { page } from '$app/stores';
/** @type {import('./$types').PageData} */
export let data;
// nós podemos acessar `data.posts` porque é retornada
// a partir da função `load` da disposição pai
$: index = data.posts.findIndex(post => post.slug === $page.params.slug);
$: next = data.posts[index - 1];
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
{#if next}
<p>Next post: <a href="/blog/{next.slug}">{next.title}</a></p>
{/if}
Se várias funções
load
retornarem dados com a mesma chave, o último 'vence' — o resultado dumaload
de disposição retornando{ a: 1, b: 2 }
e umaload
de página retornando{ b: 3, c: 4 }
seria{ a: 1, b: 3, c: 4 }
.
$page.datapermalink
O componente +page.svelte
, e cada componente +layout.svelte
acima deste, tem acesso ao seus próprios dados mais todos os dados do seus componentes pai.
Em alguns casos, podemos precisar do oposto — um disposição pai pode precisar acessar os dados da página ou dados a partir duma disposição filho. Por exemplo, a disposição de raiz pode querer acessar uma propriedade title
retornada a partir duma função load
no +page.js
ou +page.server
. Isto pode ser feito com $page.data
:
<script>
import { page } from '$app/stores';
</script>
<svelte:head>
<title>{$page.data.title}</title>
</svelte:head>
<script lang="ts">
import { page } from '$app/stores';
</script>
<svelte:head>
<title>{$page.data.title}</title>
</svelte:head>
A informação de tipo para $page.data
é fornecida pela App.PageData
.
Universal vs Servidorpermalink
Conforme vimos, existe dois tipos de função load
:
- Os ficheiros
+page.js
e+layout.js
exportam funçõesload
universais que executam ambas no servidor e no navegador - Os ficheiros
+page.server.js
e+layout.server.js
exportam funçõesload
do servidor que apenas executam no lado do servidor
Concetualmente, são a mesma coisa, mas existem algumas diferenças importantes a ter-se consciência.
Quando é Que a Função de Carregamento é Executada?permalink
As funções load
do servidor sempre executam sobre o servidor.
Por padrão, as funções load
universais executam sobre o servidor durante a interpretação do lado do servidor quando o utilizador visita a nossa página pela primeira vez. Estas então executarão novamente durante a hidratação, reutilizando quaisquer respostas das requisições de pesquisa. Todas as invocações subsequentes das funções load
universais acontecem no navegador. Nós podemos personalizar o comportamento através das opções da página. Se desativarmos a interpretação do lado do servidor, obteremos uma aplicação de página única e funções load
universais que sempre executam sobre o cliente.
Uma função load
é invocada na execução, a menos que pré-interpretemos a página — neste caso, é invocada na construção.
Entradapermalink
Ambas funções load
do servidor e universais têm acesso às propriedades descrevendo a requisição (params
, route
, e url
) e várias funções (fetch
, setHeaders
, parent
e depends
). Estas são descritas nas seguintes seções.
As funções load
do servidor são chamadas com um ServerLoadEvent
, que herda clientAddress
, cookies
, locals
, platform
, e request
da RequestEvent
.
As funções load
universais são chamadas com um LoadEvent
, que tem uma propriedade data
. Se tivermos funções load
em ambos +page.js
e +page.server.js
(ou +layout.js
e +layout.server.js
), o valor de retorno da função load
do servidor é a propriedade data
do argumento da função load
universal.
Saídapermalink
Uma função load
universal pode retornar um objeto que contém quaisquer valores, incluindo coisas como classes personalizadas e construtores de componente.
Um função load
do servidor deve retornar dados que podem ser serializados com devalue
— qualquer coisa que pode ser representada como JSON mais coisas como BigInt
, Date
, Map
, Set
e RegExp
, ou referências repetidas ou cíclicas — para que possa ser transportado sobre a rede. Nossos dados podem incluir promessas, casos em que serão transmitidos aos navegadores.
Quando Usar Qualpermalink
As funções load
do servidor são convenientes quando precisamos acessar os dados diretamente a partir duma base de dados ou sistema de ficheiro, ou precisamos usar variáveis de ambiente privadas.
As funções load
universais são úteis quando precisamos pedir dados duma API externa e não precisamos de credenciais privadas, uma vez que a SvelteKit pode obter os dados diretamente da API ao invés ir através do nosso servidor. Estas também são úteis quando precisamos retornar algo que não pode ser serializado, tais como um construtor de componente da Svelte.
Em raros casos, podemos precisar usar ambas em conjunto — por exemplo, podemos precisar retornar uma instância duma classe personalizada que era inicializada com os dados do nosso servidor.
Usando Dados da URLpermalink
Frequentemente a função load
depende da URL duma maneira ou outra. Para isto, a função load
fornece-nos com url
, route
, e params
.
urlpermalink
Uma instância do URL
, que contém propriedades como a origin
, hostname
, pathname
e searchParams
(que contém sequência de caracteres de consulta analisados sintaticamente como um objeto URLSearchParams
). url.hash
não pode ser acessado durante a load
, uma vez que está indisponível no servidor.
Em alguns ambientes isto é derivado a partir dos cabeçalhos da requisição durante a interpretação do lado do servidor. Se estivermos usando a
adapter-node
, por exemplo, podemos precisar configurar o adaptador para a URL ficar correta.
routepermalink
Contém o nome do diretório da rota atual, relativo ao src/routes
:
ts
/** @type {import('./$types').PageLoad} */export functionload ({route }) {console .log (route .id ); // '/a/[b]/[...c]'}
ts
import type {PageLoad } from './$types';export constload :PageLoad = ({route }) => {console .log (route .id ); // '/a/[b]/[...c]'};
paramspermalink
params
é derivado a partir da url.pathname
e route.id
.
Dada uma route.id
de /a/[b]/[...c]
e uma url.pathname
de /a/x/y/z
, o objeto params
parecer-se-ia com isto:
ts
{"b": "x","c": "y/z"}
Making fetch requestspermalink
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 with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
andauthorization
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
andjson
methods of theResponse
object. Note that headers will not be serialized, unless explicitly included viafilterSerializedResponseHeaders
. - 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 theload
fetch
, this is why.
ts
/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,params }) {constres = awaitfetch (`/api/items/${params .id }`);constitem = awaitres .json ();return {item };}
ts
import type {PageLoad } from './$types';export constload :PageLoad = async ({fetch ,params }) => {constres = awaitfetch (`/api/items/${params .id }`);constitem = awaitres .json ();return {item };};
Cookiespermalink
A server load
function can get and set cookies
.
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload ({cookies }) {constsessionid =cookies .get ('sessionid');return {user : awaitdb .getUser (sessionid )};}
ts
import * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload :LayoutServerLoad = async ({cookies }) => {constsessionid =cookies .get ('sessionid');return {user : awaitdb .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.dom 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 to work around it.
When setting cookies, be aware of the
path
property. By default, thepath
of a cookie is the current pathname. If you for example set a cookie at pageadmin/user
, the cookie will only be available within theadmin
pages by default. In most cases you likely want to setpath
to'/'
to make the cookie available throughout your app.
Headerspermalink
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:
ts
/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,setHeaders }) {consturl = `https://cms.example.com/products.json`;constresponse = awaitfetch (url );// cache the page for the same length of time// as the underlying datasetHeaders ({age :response .headers .get ('age'),'cache-control':response .headers .get ('cache-control')});returnresponse .json ();}
ts
import type {PageLoad } from './$types';export constload :PageLoad = async ({fetch ,setHeaders }) => {consturl = `https://cms.example.com/products.json`;constresponse = awaitfetch (url );// cache the page for the same length of time// as the underlying datasetHeaders ({age :response .headers .get ('age'),'cache-control':response .headers .get ('cache-control'),});returnresponse .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 datapermalink
Occasionally it's useful for a load
function to access data from a parent load
function, which can be done with await parent()
:
ts
/** @type {import('./$types').LayoutLoad} */export functionload () {return {a : 1 };}
ts
import type {LayoutLoad } from './$types';export constload :LayoutLoad = () => {return {a : 1 };};
ts
/** @type {import('./$types').LayoutLoad} */export async functionload ({parent }) {const {a } = awaitparent ();return {b :a + 1 };}
ts
import type {LayoutLoad } from './$types';export constload :LayoutLoad = async ({parent }) => {const {a } = awaitparent ();return {b :a + 1 };};
ts
/** @type {import('./$types').PageLoad} */export async functionload ({parent }) {const {a ,b } = awaitparent ();return {c :a +b };}
ts
import type {PageLoad } from './$types';export constload :PageLoad = async ({parent }) => {const {a ,b } = awaitparent ();return {c :a +b };};
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<!-- renders `1 + 2 = 3` -->
<p>{data.a} + {data.b} = {data.c}</p>
Notice that the
load
function in+page.js
receives the merged data from both layoutload
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.
/** @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 }
};
}
Errorspermalink
If an error is thrown during load
, the nearest +error.svelte
will be rendered. For expected errors, use the error
helper from @sveltejs/kit
to specify the HTTP status code and an optional message:
ts
import {error } from '@sveltejs/kit';/** @type {import('./$types').LayoutServerLoad} */export functionload ({locals }) {if (!locals .user ) {throwerror (401, 'not logged in');}if (!locals .user .isAdmin ) {throwerror (403, 'not an admin');}}
ts
import {error } from '@sveltejs/kit';import type {LayoutServerLoad } from './$types';export constload :LayoutServerLoad = ({locals }) => {if (!locals .user ) {throwerror (401, 'not logged in');}if (!locals .user .isAdmin ) {throwerror (403, 'not an admin');}};
If an unexpected error is thrown, SvelteKit will invoke handleError
and treat it as a 500 Internal Error.
Redirectspermalink
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.
ts
import {redirect } from '@sveltejs/kit';/** @type {import('./$types').LayoutServerLoad} */export functionload ({locals }) {if (!locals .user ) {throwredirect (307, '/login');}}
ts
import {redirect } from '@sveltejs/kit';import type {LayoutServerLoad } from './$types';export constload :LayoutServerLoad = ({locals }) => {if (!locals .user ) {throwredirect (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
from $app.navigation
.
Streaming with promisespermalink
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:
ts
/** @type {import('./$types').PageServerLoad} */export functionload () {return {one :Promise .resolve (1),two :Promise .resolve (2),streamed : {three : newPromise ((fulfil ) => {setTimeout (() => {fulfil (3)}, 1000);})}};}
ts
import type {PageServerLoad } from './$types';export constload :PageServerLoad = () => {return {one :Promise .resolve (1),two :Promise .resolve (2),streamed : {three : newPromise ((fulfil ) => {setTimeout (() => {fulfil (3);}, 1000);}),},};};
This is useful for creating skeleton loading states, for example:
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<p>
one: {data.one}
</p>
<p>
two: {data.two}
</p>
<p>
three:
{#await data.streamed.three}
Loading...
{:then value}
{value}
{:catch error}
{error.message}
{/await}
</p>
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<p>
one: {data.one}
</p>
<p>
two: {data.two}
</p>
<p>
three:
{#await data.streamed.three}
Loading...
{:then value}
{value}
{:catch error}
{error.message}
{/await}
</p>
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 loadingpermalink
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 functionspermalink
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...
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
ts
import * asdb from '$lib/server/database';import type {PageServerLoad } from './$types';export constload :PageServerLoad = async ({params }) => {return {post : awaitdb .getPost (params .slug ),};};
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
ts
import * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload :LayoutServerLoad = async () => {return {posts : awaitdb .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 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 invalidationpermalink
You can also rerun load
functions that apply to the current page using invalidate(url)
, which reruns all load
functions that depend on url
, and 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]:
:
ts
/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,depends }) {// load reruns when `invalidate('https://api.example.com/random-number')` is called...constresponse = awaitfetch ('https://api.example.com/random-number');// ...or when `invalidate('app:random')` is calleddepends ('app:random');return {number : awaitresponse .json ()};}
ts
import type {PageLoad } from './$types';export constload :PageLoad = async ({fetch ,depends }) => {// load reruns when `invalidate('https://api.example.com/random-number')` is called...constresponse = awaitfetch ('https://api.example.com/random-number');// ...or when `invalidate('app:random')` is calleddepends ('app:random');return {number : awaitresponse .json (),};};
<script>
import { invalidate, invalidateAll } from '$app/navigation';
/** @type {import('./$types').PageData} */
export let data;
function rerunLoadFunction() {
// any of these will cause the `load` function to rerun
invalidate('app:random');
invalidate('https://api.example.com/random-number');
invalidate(url => url.href.includes('random-number'));
invalidateAll();
}
</script>
<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>
<script lang="ts">
import { invalidate, invalidateAll } from '$app/navigation';
import type { PageData } from './$types';
export let data: PageData;
function rerunLoadFunction() {
// any of these will cause the `load` function to rerun
invalidate('app:random');
invalidate('https://api.example.com/random-number');
invalidate((url) => url.href.includes('random-number'));
invalidateAll();
}
</script>
<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>
When do load functions rerun?permalink
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 asurl.pathname
orurl.search
) whose value has changed. Properties inrequest.url
are not tracked - It calls
await parent()
and a parentload
function reran - It declared a dependency on a specific URL via
fetch
(universal load only) ordepends
, and that URL was marked invalid withinvalidate(url)
- All active
load
functions were forcibly rerun withinvalidateAll()
params
and url
can change in response to a <a href="..">
link click, a <form>
interaction, a goto
invocation, or a 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
callback, and/or wrap your component in a {#key ...}
block.