Skip to main content

Conceitos Principais

Roteamento

Editar esta página na GitHub

No coração da SvelteKit está um roteador baseado no sistema de ficheiro. As rotas da nossa aplicação — ou seja, os caminhos de URL que os utilizadores podem acessar — são definidos pelos diretórios na nossa base de código:

  • src/routes é a rota de raiz
  • src/routes/about cria uma rota /about
  • src/routes/blog/[slug] cria uma rota com um parâmetro, slug, que pode ser usado para carregar os dados dinamicamente quando o utilizador requisitar uma página como /blog/hello-world

Nós podemos mudar src/routes para um diretório diferente editando a configuração do projeto.

Cada diretório de rota contem um ou mais ficheiros de rota, que podem ser identificados por seu prefixo +.

+page

+page.svelte

Um componente +page.svelte define uma página da nossa aplicação. Por padrão, as páginas são interpretadas em ambos no servidor (SSR) para a requisição inicial e no navegador (CSR) para a navegação subsequente:

src/routes/+page.svelte
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
src/routes/about/+page.svelte
<h1>About this site</h1>
<p>TODO...</p>
<a href="/">Home</a>
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>

Nota que a SvelteKit usa os elementos <a> para navegar entre as rotas, ao invés dum componente <Link> específico da abstração.

+page.js

Muitas vezes, uma página precisará de carregar alguns dados antes de puder ser desenhada. Para isto, adicionamos um módulo +page.js que exporta uma função load:

src/routes/blog/[slug]/+page.js
ts
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');
}
src/routes/blog/[slug]/+page.ts
ts
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = ({ 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');
};

Esta função executa ao lado do +page.svelte, o que significa que executa no servidor durante a interpretação do lado do servidor e no navegador durante a navegação do lado do cliente. Consulte load por detalhes completos da API.

Bem como load, +page.js pode exportar valores que configuram o comportamento da página:

  • export const prerender = true ou false ou 'auto'
  • export const ssr = true ou false
  • export const csr = true ou false

Nós podemos encontrar mais informação sobre estes nas opções da página.

+page.server.js

Se a nossa função load puder apenas executar no servidor — por exemplo, se precisa de requisitar dados a partir duma base de dados ou precisamos acessar variáveis de ambiente privadas como chaves de API — então podemos renomear +page.js para +page.server.js e mudar o tipo de PageLoad para PageServerLoad:

src/routes/blog/[slug]/+page.server.js
ts
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');
}
src/routes/blog/[slug]/+page.server.ts
ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
throw error(404, 'Not found');
};

Durante a navegação do lado do cliente, a SvelteKit carregará este dado a partir do servidor, o que significa que o valor retornado deve ser serivalizável usando devalue. Consulte a load por detalhes completos da API.

Tal como +page.js, +page.server.js pode exportar as opções da páginaprerender, ssr, e csr.

Um ficheiro +page.server.js também pode exportar ações. Se load permite-nos ler dados do servidor, actions permite-nos escrever dados ao servidor usando o elemento <form>. Para aprender como usá-los consulte a seção ações de formulário.

+error

Se um erro ocorrer durante a load, a SvelteKit desenhará uma página de erro padrão. Nós podemos personalizar esta página de erro por rota, adicionando um ficheiro +error.svelte:

src/routes/blog/[slug]/+error.svelte
<script>
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>
src/routes/blog/[slug]/+error.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>

A SvelteKit 'subirá a árvore' à procura do limite de erro mais próximo — se o ficheiro acima não existisse, tentaria src/routes/blog/+error.svelte e depois src/routes/+error.svelte antes de desenhar a página de erro padrão. Se isto falhar (se o erro foi lançado a partir da função load do +layout de raiz, que situa-se 'acima' do +error de raiz), a SvelteKit sairá e desenhará uma página de erro de retrocesso estático, que podemos personalizar criando um ficheiro src/error.html.

Se o erro ocorrer dentro duma função load no +layout(.server).js, o limite do erro mais próximo na árvore é um ficheiro +error.svelte acima desta disposição (não próximo à mesma).

Se nenhuma rota puder ser encontrada (404), src/routes/+error.svelte (ou a página de erro padrão, se este ficheiro não existir) será usado.

+error.svelte não é usado quando um erro ocorre dentro de handle ou um manipulador de requisição do +server.js.

Nós podemos ler mais sobre a manipulação de erro nesta ligação.

+layout

Até ao momento, tratamos as páginas como componentes inteiramente autónomos — sobre a navegação, o componente +page.svelte existente será destruído, e um novo ocupará o seu lugar.

Mas em muitas aplicações, existem elementos que devem ser visíveis em todas as páginas, tais como navegação de alto nível ou um rodapé. No lugar de repeti-los em toda +page.svelte, podemos colocá-los nas disposições.

+layout.svelte

Para criar uma disposição que aplica-se à todas as páginas, críamos um ficheiro chamado de src/routes/+layout.svelte. A disposição padrão (aquela que a Svelte usa se não trouxermos a nossa própria) parece-se com isto...:

<slot></slot>

...mas podemos adicionar qualquer marcação, estilos e comportamento que quisermos. O único requisito é que o componente inclua um <slot> para o conteúdo da página. Por exemplo, vamos adicionar uma barra de navegação:

src/routes/+layout.svelte
<nav>
	<a href="/">Home</a>
	<a href="/about">About</a>
	<a href="/settings">Settings</a>
</nav>

<slot></slot>

Se criarmos páginas para /, /about e /settings...:

src/routes/+page.svelte
<h1>Home</h1>
src/routes/about/+page.svelte
<h1>About</h1>
src/routes/settings/+page.svelte
<h1>Settings</h1>

...a navegação sempre estará visível, e clicando entre as três páginas apenas resultará no <h1> sendo substituído.

As disposições podem ser encaixadas. Suponhamos que não apenas temos uma única página /settings, mas ao invés disto temos páginas encaixadas como /settings/profile e settings/notifications com um submenu partilhado (para um exemplo da vida real, consulte a github.com/settings).

Nós podemos criar uma disposição que apenas aplica-se às páginas abaixo de /settings (enquanto herdamos a disposição de raiz com a navegação de alto nível):

src/routes/settings/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>
src/routes/settings/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>

Por padrão, cada disposição herda a disposição acima da mesma. Algumas vezes que isto não é o que queremos - neste caso, as disposições avançadas podem ajudar-nos.

+layout.js

Tal como +page.svelte carrega dados a partir de +page.js, o nosso componente +layout.svelte pode receber dados a partir duma função load no +layout.js:

src/routes/settings/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export function load() {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' }
]
};
}
src/routes/settings/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' },
],
};
};

Se um +layout.js exportar opções de páginaprerender, ssr e csr — serão usadas como padrões para as páginas do filho.

Os dados retornados a partir duma função load da disposição também está disponível em todas as páginas do seu filho:

src/routes/settings/profile/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;

	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
src/routes/settings/profile/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>

Muitas vezes, os dados da disposição estão inalterados quando navegamos entre as páginas. a Svelte executará novamente de maneira inteligente as funções load quando necessário.

+layout.server.js

Para executar a função load da nossa disposição no servidor, a movemos para +layout.server.js, e mudamos o tipo de LayoutLoad para LayoutServerLoad.

Tal como +layout.js, +layout.server.js pode exportar as opções de páginaprerender, ssr e csr.

+server

Bem como as páginas, podemos definir rotas com um ficheiro +server.js (algumas vezes remete-nos à como uma 'rota da API' ou um 'destino'), que dá-nos o controlo total sobre a resposta. O nosso ficheiro +server.js exporta funções correspondentes aos verbos de HTTP como GET, POST, PATCH, PUT, DELETE, e OPTIONS que recebem um argumento RequestEvent e retornam um objeto de Response.

Por exemplo, poderíamos criar uma rota api/random-number com um manipulador de GET:

src/routes/api/random-number/+server.js
ts
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));
}
src/routes/api/random-number/+server.ts
ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = ({ 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));
};

O primeiro argumento para Response pode ser um ReadableStream, tornando possível fluir grandes quantidades de dados ou criar eventos enviados do servidor (a menos que implementemos em produção em plataformas que amortecem as respostas, tal como AWS Lambda).

Nós podemos usar os métodos error, redirect e json do @sveltejs/kit por conveniência (mas não temos de o fazer).

Se um erro for lançado (ou throw erro(...) ou um erro inesperado), a resposta será uma representação de JSON do erro ou uma página de erro de retrocesso — que pode ser personalizada através de src/error.html — dependendo do cabeçalho Accept. O componente +error.svelte não será desenhado neste caso. Nós podemos ler mais sobre a manipulação de erro nesta ligação.

Quando críamos um manipulador de OPTIONS, nota que a Vite injetará os cabeçalhos Access-Control-Allow-Origin e Access-Control-Allow-Methods — estes não estarão presentes em produção a menos que os adicionemos.

Recebendo Dados

Com a exportação dos manipuladores de POST/PUT/PATCH/DELETE/OPTIONS, os ficheiros +server.js podem ser usados para criar uma API completa:

src/routes/add/+page.svelte
<script>
	let a = 0;
	let b = 0;
	let total = 0;

	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json'
			}
		});

		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/add/+page.svelte
<script lang="ts">
	let a = 0;
	let b = 0;
	let total = 0;
	
	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json',
			},
		});
	
		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/api/add/+server.js
ts
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
src/routes/api/add/+server.ts
ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => {
const { a, b } = await request.json();
return json(a + b);
};

Em geral, as ações de formulário são uma maneira melhor de submeter os dados a partir dos navegadores ao servidor.

Se um manipulador de GET for exportado, uma requisição de HEAD retornará o content-length do corpo da resposta do manipulador de GET.

Manipulador de Método de Retrocesso

Exportar o manipulador de fallback corresponderá à quaisquer métodos de requisição não manipulada, incluindo métodos como MOVE que não tem nenhuma exportação dedicada a partir de +server.js:

src/routes/api/add/+server.js
ts
import { json, text } from '@sveltejs/kit';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// Este manipulador responderá aos PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
return text(`I caught your ${request.method} request!`);
}
src/routes/api/add/+server.ts
ts
import { json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// Este manipulador responderá aos PUT, PATCH, DELETE, etc.
export const fallback: RequestHandler = async ({ request }) => {
return text(`I caught your ${request.method} request!`);
};

Para requisições de HEAD, o manipulador de GET tem precedência sobre o manipulador de fallback.

Negociação de Conteúdo

Os ficheiros +server.js podem ser colocados no mesmo diretório que os ficheiros +page, permitindo a mesma rota ser ou uma página ou um destino de API. Para determinar quais, a SvelteKit aplica as seguintes regras:

  • As requisições de PUT/PATCH/DELETE/OPTIONS são sempre manipuladas pelo +server.js uma vez que não aplicam-se às páginas
  • As requisições de GET/POST são tratadas como requisições de página se o cabeçalho accept priorizar text/html (em outras palavras, é uma requisição de página do navegador), ou são manipuladas pelo +server.js

$types

Em todos os exemplos acima, estávamos a importar os tipos a partir dum ficheiro $types.d.ts. Este é um ficheiro que a SvelteKit cria para nós num diretório escondido se estivermos a usar a TypeScript (ou JavaScript com as anotações de tipo da JSDoc) para dar-nos segurança de tipo quando trabalhamos com os nossos ficheiros de raiz.

Por exemplo, anotar export let data com PageData (ou LayoutData, para um ficheiro +layout.svelte) diz a TypeScript que o tipo do data é tudo aquilo que foi retornado a partir da load:

src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

Como consequência, anotar a função load com PageLoad, PageServerLoad, LayoutLoad ou LayoutServerLoad (para +page.js, +page.server.js, +layout.js e +layout.server.js respetivamente) garante que o params e o valor de retorno estão corretamente tipificados.

Se estivermos a usar o VS Code ou qualquer ambiente de desenvolvimento integrado que suporta o protocolo de servidor de linguagem e extensões de TypeScript então podemos omitir estes tipos inteiramente! O ferramental de ambiente de desenvolvimento integrado da Svelte inserirá os tipos corretos para nós, assim receberemos a verificação de tipo sem as escrevermos por conta própria. Também funciona com a nossa ferramenta da linha de comando svelte-check.

Nós podemos ler mais sobre a omissão de $types na nossa publicação de blogue sobre isto.

Outros Ficheiros

Quaisquer outros ficheiros dentro do diretório de rota são ignorados pela SvelteKit. Isto significa que podemos colocar componentes e módulos utilitários com as rotas que precisam deles.

Se os componentes e módulos forem necessários por várias rotas, é uma boa ideia colocá-los no $lib.

Leituras Avançadas