Pular para o conteúdo principal

Desenvolvimento Svelte e SvelteKit no Cursor: Um Guia Completo

Desenvolvimento Svelte no Cursor

Svelte é um framework de UI baseado em compilador que envia JavaScript mínimo para o navegador, e SvelteKit é seu framework de aplicação full-stack. O Svelte 5 introduziu runes, um novo modelo de reatividade mais explícito e poderoso do que a reatividade baseada em let anterior. O Cursor, com sua base compatível com VS Code e assistente de IA integrado, é um excelente editor para trabalho Svelte — ele entende arquivos .svelte, fornece IntelliSense rico e pode gerar componentes a partir de prompts em linguagem natural.

Este guia cobre o fluxo de trabalho completo de desenvolvimento Svelte no Cursor: configuração da cadeia de ferramentas, domínio dos runes do Svelte 5, construção de aplicações SvelteKit com roteamento baseado em arquivos, gerenciamento de estado compartilhado com stores e aproveitamento da IA para gerar e refatorar componentes.

Configurando o Ambiente de Desenvolvimento Svelte no Cursor​

Pré-requisitos​

Svelte 5 e SvelteKit exigem Node.js 18 ou superior. Verifique sua instalação:

node --version
npm --version

Se precisar de uma versão mais recente, use fnm ou nvm:

fnm install 20
fnm use 20
dica

O terminal integrado do Cursor (Ctrl+`) lembra seu diretório de trabalho atual, então você pode fazer scaffold de um projeto e abrir arquivos imediatamente sem mudar de contexto.

Instalando a Extensão Svelte​

O Cursor depende da extensão oficial Svelte para suporte de linguagem. Instale-a a partir da vista de Extensões (Ctrl+Shift+X):

  1. Pesquise por "Svelte".
  2. Instale a extensão publicada pela equipe Svelte.
  3. Recarregue o Cursor se solicitado.

A extensão fornece:

  • Realce de sintaxe para arquivos .svelte.
  • Diagnóstico e verificação de tipos via svelte-language-server.
  • Formatação através do Prettier com o pacote prettier-plugin-svelte.
  • IntelliSense para imports de componentes e props.
aviso

Se você usou anteriormente uma extensão Svelte da comunidade, desative-a antes de instalar a oficial. Extensões conflitantes causam diagnósticos duplicados e formatação não confiável.

Scaffold de um Projeto SvelteKit​

Use o CLI oficial sv para criar uma nova aplicação SvelteKit:

npx sv create my-svelte-app

O prompt interativo oferece predefinições:

? Which template would you like? SvelteKit minimal
? Add type checking with TypeScript? Yes, using TypeScript syntax
? Add ESLint for code linting? Yes
? Add Prettier for code formatting? Yes
? Add Playwright for browser testing? No
? Add Vitest for unit testing? Yes

Abra o projeto no Cursor:

cd my-svelte-app
cursor .
npm install
npm run dev

O servidor de desenvolvimento inicia em http://localhost:5173.

dica

Você pode adicionar recursos posteriormente sem recriar o projeto. O comando sv add instala integrações como Tailwind CSS, Storybook ou Drizzle em um aplicativo existente:

npx sv add tailwindcss
npx sv add storybook

Dominando os Runes do Svelte 5​

O Que São Runes?​

Runes são funções especiais reconhecidas pelo compilador introduzidas no Svelte 5. Elas substituem a reatividade implícita do Svelte 4 por um modelo explícito e previsível. Os runes principais são:

  • $state — declara estado reativo.
  • $derived — declara um valor calculado a partir de outro estado reativo.
  • $effect — executa efeitos colaterais quando as dependências mudam.
  • $props — declara props de componente.
  • $bindable — marca uma prop como vinculável bidirecionalmente.

Declarando Estado com $state​

<script lang="ts">
let count = $state(0);
let user = $state({ name: 'Ada', age: 36 });

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count: {count}
</button>
<p>{user.name} is {user.age}</p>
dica

No Svelte 5, os manipuladores de eventos usam o atributo onclick (minúsculas, sem prefixo on:) em vez do antigo on:click. A nova sintaxe é mais próxima dos atributos DOM padrão e funciona melhor com TypeScript.

Valores Derivados com $derived​

<script lang="ts">
let price = $state(100);
let quantity = $state(2);
let total = $derived(price * quantity);
let formatted = $derived(
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(total)
);
</script>

<p>Price: $ {price}</p>
<p>Quantity: {quantity}</p>
<p>Total: {formatted}</p>

Efeitos Colaterais com $effect​

<script lang="ts">
let theme = $state<'light' | 'dark'>('light');

$effect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
});
</script>

<button onclick={() => (theme = theme === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
aviso

$effect é executado depois que o DOM foi atualizado. Não o use para calcular valores que afetam a renderização — use $derived. Effects servem para sincronizar com sistemas externos (APIs, localStorage, bibliotecas de terceiros).

Props de Componente com $props​

<script lang="ts">
interface Props {
title: string;
count?: number;
onIncrement?: (value: number) => void;
}

let { title, count = 0, onIncrement }: Props = $props();

function handleClick() {
count++;
onIncrement?.(count);
}
</script>

<h2>{title}</h2>
<button onclick={handleClick}>Clicked {count} times</button>

Vinculação Bidirecional com $bindable​

<!-- TextInput.svelte -->
<script lang="ts">
interface Props {
value?: string;
}
let { value = $bindable('') }: Props = $props();
</script>

<input bind:value />

Uso em um pai:

<script lang="ts">
import TextInput from './TextInput.svelte';
let name = $state('');
</script>

<TextInput bind:value={name} />
<p>Hello, {name}!</p>
dica

$bindable é a substituição do Svelte 5 para o padrão export let + bind:value. Ele torna o contrato de vinculação bidirecional explícito na assinatura de tipo do componente filho.

Construindo Aplicações SvelteKit​

Roteamento Baseado em Arquivos​

O SvelteKit usa roteamento baseado em arquivos dentro do diretório src/routes. Cada arquivo +page.svelte se torna uma página, e arquivos +layout.svelte envolvem páginas aninhadas.

src/routes/
├── +layout.svelte
├── +page.svelte → /
├── about/
│ └── +page.svelte → /about
├── blog/
│ ├── +page.svelte → /blog
│ └── [slug]/
│ └── +page.svelte → /blog/:slug
└── +layout.svelte

Carregando Dados com +page.server.ts​

// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

interface Post {
slug: string;
title: string;
body: string;
}

export const load: PageServerLoad = async ({ params }) => {
const response = await fetch(`https://api.example.com/posts/${params.slug}`);
if (!response.ok) {
throw error(404, 'Post not found');
}
const post: Post = await response.json();
return { post };
};

Consumindo os dados na página:

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

<article>
<h1>{data.post.title}</h1>
<p>{data.post.body}</p>
</article>
dica

O módulo $types é gerado automaticamente pelo SvelteKit. Ele fornece funções load e props data totalmente tipadas. O IntelliSense do Cursor lê esses tipos automaticamente, então você obtém autocompletar para data.post.title.

Ações de Formulário​

O SvelteKit lida com envios de formulário com ações definidas em +page.server.ts:

// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
default: async ({ request, cookies }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');

if (!email || !password) {
return fail(400, { message: 'Missing credentials' });
}

const token = await authenticate(String(email), String(password));
if (!token) {
return fail(401, { message: 'Invalid credentials' });
}

cookies.set('session', token, { path: '/' });
throw redirect(303, '/dashboard');
},
};

A página correspondente usa a ação enhance para aprimoramento progressivo:

<!-- src/routes/login/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageData } from './$types';
let { form }: { form: PageData } = $props();
</script>

<form method="POST" use:enhance>
<label>Email <input name="email" type="email" /></label>
<label>Password <input name="password" type="password" /></label>
{#if form?.message}
<p class="error">{form.message}</p>
{/if}
<button type="submit">Log in</button>
</form>
aviso

Arquivos +page.server.ts do lado do servidor são executados apenas no servidor. Nunca importe APIs apenas do navegador (como localStorage) neles, e nunca coloque segredos em +page.ts (o load universal), que também pode rodar no navegador.

Layouts e Grupos de Layout​

<!-- src/routes/+layout.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
import Nav from '../components/Nav.svelte';

let { children }: { children: Snippet } = $props();
</script>

<div class="app">
<Nav />
<main>{@render children()}</main>
</div>

No Svelte 5, o snippet children substitui a sintaxe <slot /> obsoleta.

Gerenciando Estado Compartilhado com Stores​

Básico de Svelte Stores​

Mesmo com runes, os stores do Svelte permanecem úteis para estado entre componentes. Um store é qualquer objeto com um método subscribe.

// lib/stores/user.ts
import { writable } from 'svelte/store';

interface User {
id: string;
name: string;
}

export const currentUser = writable<User | null>(null);

Consumindo o store com o prefixo $:

<script lang="ts">
import { currentUser } from '$lib/stores/user';
</script>

{#if $currentUser}
<p>Welcome, {$currentUser.name}</p>
{:else}
<p>Please log in.</p>
{/if}

Stores Readable e Derived​

import { readable, derived } from 'svelte/store';

export const time = readable(new Date(), (set) => {
const interval = setInterval(() => set(new Date()), 1000);
return () => clearInterval(interval);
});

export const formattedTime = derived(time, ($time) =>
$time.toLocaleTimeString('en-US')
);

Combinando Runes e Stores​

Você pode usar stores dentro de $effect para fazer a ponte entre código legado e runes:

<script lang="ts">
import { currentUser } from '$lib/stores/user';
import { get } from 'svelte/store';

let userSnapshot = $state(get(currentUser));

$effect(() => {
const unsubscribe = currentUser.subscribe((value) => {
userSnapshot = value;
});
return unsubscribe;
});
</script>
dica

Para novos projetos, prefira um padrão de estado compartilhado baseado em runes usando uma classe com campos $state e a API de context do Svelte. Isso evita o boilerplate do store mantendo a reatividade:

// lib/state/counter.svelte.ts
export class CounterState {
count = $state(0);
increment() {
this.count++;
}
}

Geração de Componentes Svelte Assistida por IA​

Gerando Componentes a Partir de Prompts​

A IA do Cursor é excelente na produção de componentes Svelte 5 com runes. Abra um chat (Ctrl+Shift+P → "Cursor: New Chat") ou use edições inline (Ctrl+K) e descreva o que você precisa:

Create a Svelte 5 component (script lang="ts") for an accessible accordion.
It should accept an items prop: array of { id, title, content }. Use runes
for state (which panel is open). Include keyboard navigation (arrow keys)
and scoped styles.

O Cursor gera um arquivo .svelte completo. Revise a saída e itere — peça para adicionar uma prop allowMultiple ou transições de animação.

Migrando do Svelte 4 para o Svelte 5​

Se você mantém uma base de código mais antiga, o Cursor pode migrar componentes para a sintaxe de runes. Destaque o arquivo, pressione Ctrl+K e digite:

Migrate this Svelte 4 component to Svelte 5 runes syntax. Replace `let`
reactivity with $state, `$:` with $derived/$effect, `export let` with $props,
and `createEventDispatcher` with callback props. Preserve all behavior.
aviso

A migração de runes muda o tempo de reatividade de maneiras sutis. Após o refatoramento, execute sua suíte de testes e verifique manualmente qualquer componente que use hooks de ciclo de vida ou watchers, pois $effect dispara sob condições diferentes do antigo afterUpdate.

Gerando Testes de Ponta a Ponta​

Emparelhe o Playwright com testes gerados por IA:

// tests/accordion.spec.ts
import { test, expect } from '@playwright/test';

test('accordion expands on click', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Section 1' }).click();
await expect(page.getByText('Content of section 1')).toBeVisible();
});

Peça ao Cursor: "Generate Playwright tests for Accordion.svelte covering expand/collapse and keyboard navigation."

Configurando o Cursor para Projetos Svelte​

Configurações do Workspace​

Crie .vscode/settings.json para padronizar a formatação em toda a equipe:

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "svelte.svelte-vscode",
"svelte.plugin.svelte.format.config.singleQuote": true,
"svelte.plugin.svelte.format.config.semi": true,
"svelte.plugin.svelte.note-new-transformation": false,
"typescript.tsdk": "node_modules/typescript/lib"
}

Prettier com Plugin Svelte​

npm install -D prettier prettier-plugin-svelte

.prettierrc:

{
"singleQuote": true,
"semi": true,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": { "parser": "svelte" }
}
]
}
dica

O Prettier formata os blocos <script> e <style>, mas deixa intencionalmente a marcação de template Svelte intacta para evitar quebrar diretivas. Para formatação de template, dependa do formatador embutido da extensão Svelte.

Configuração ESLint​

// eslint.config.js
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import tsParser from 'typescript-eslint';

export default [
js.configs.recommended,
...svelte.configs['flat/recommended'],
{
languageOptions: {
parserOptions: {
extraFileExtensions: ['.svelte'],
},
},
},
];

Depuração e Desempenho​

Svelte DevTools​

Instale a extensão de navegador Svelte DevTools para inspecionar hierarquias de componentes e estado. Embora o Cursor hospede o editor, os DevTools do navegador mostram valores de runtime que são críticos para depuração de runes.

Source Maps em Produção​

O SvelteKit gera source maps por padrão. Confirme em svelte.config.js:

import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
};

export default config;
aviso

Ao implantar em plataformas serverless, verifique se seu adapter trata source maps corretamente. Os adapters adapter-node e adapter-vercel se comportam de maneira diferente — revise a documentação do adapter antes de enviar source maps para produção.

Análise de Bundle​

Adicione rollup-plugin-visualizer para inspecionar o tamanho do bundle:

// vite.config.ts
import { sveltekit } from '@sveltejs/vite-plugin-sveltekit';
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
plugins: [
sveltekit(),
visualizer({
filename: 'stats.html',
open: true,
gzipSize: true,
}),
],
});

Melhores Práticas para Svelte no Cursor​

Estrutura do Projeto​

src/
├── lib/
│ ├── components/
│ │ ├── Accordion.svelte
│ │ └── TextInput.svelte
│ ├── stores/
│ │ └── user.ts
│ ├── state/
│ │ └── counter.svelte.ts
│ └── utils/
├── routes/
│ ├── +layout.svelte
│ └── +page.svelte
└── app.html

Convenções de Nomenclatura para Arquivos Runes​

O Svelte 5 introduz a extensão .svelte.ts (e .svelte.js) para módulos que usam runes fora de componentes. Nomeie os arquivos de estado compartilhado adequadamente:

// src/lib/state/cart.svelte.ts
export class CartState {
items = $state<{ id: number; qty: number }[]>([]);

get total() {
return this.items.reduce((sum, i) => sum + i.qty, 0);
}

add(id: number, qty = 1) {
this.items.push({ id, qty });
}
}
dica

O sufixo .svelte.ts diz ao compilador Svelte para processar runes dentro de módulos TypeScript comuns. Sem ele, $state é tratado como uma função indefinida e o build falha.

Acessibilidade por Padrão​

O compilador do Svelte avisa sobre alt ausente, aria-* e associações de rótulo. Trate esses avisos como erros em CI:

// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const config = {
preprocess: vitePreprocess(),
kit: { adapter: adapter() },
compilerOptions: {
accessors: false,
},
};

export default config;

Armadilhas Comuns e Como Evitá-las​

Uso de Sintaxe de Evento Legada​

O Svelte 5 descontinua on:click em favor de onclick. Misturar ambos funciona durante a transição, mas produz avisos. Padronize na nova sintaxe:

<!-- Svelte 4 (descontinuado) -->
<button on:click={handler}>Click</button>

<!-- Svelte 5 (recomendado) -->
<button onclick={handler}>Click</button>

Modificar Props​

Props são somente leitura por padrão. Use $bindable para vinculação bidirecional ou emita callbacks:

<script lang="ts">
interface Props {
value: string;
onChange?: (v: string) => void;
}
let { value, onChange }: Props = $props();

function handleInput(e: Event) {
const target = e.target as HTMLInputElement;
onChange?.(target.value);
}
</script>

<input value={value} oninput={handleInput} />

Uso Excessivo de $effect​

Effects devem sincronizar com sistemas externos, não calcular valores de renderização. Se você se pegar escrevendo $effect para atualizar uma variável usada no template, mude para $derived:

<script lang="ts">
let a = $state(1);
let b = $state(2);

// ERRADO — effect para valor derivado
// let sum = $state(0);
// $effect(() => { sum = a + b; });

// CORRETO — derivado
let sum = $derived(a + b);
</script>
aviso

Effects que dependem de si mesmos podem criar loops infinitos. O compilador Svelte detecta alguns casos, mas sempre verifique se as dependências do seu effect são finitas e terminantes.

Resumo​

O Cursor é um ambiente poderoso para desenvolvimento Svelte e SvelteKit. A extensão oficial Svelte fornece criação type-safe e formatação, os runes do Svelte 5 trazem reatividade explícita e previsível, e o roteamento baseado em arquivos do SvelteKit com funções load tipadas acelera o trabalho full-stack. Ao combinar essas ferramentas com o assistente de IA do Cursor para geração de componentes, refatoração e scaffolding de testes, você pode construir aplicações Svelte modernas de forma eficiente. Comece com sv create, fixe sua configuração Prettier e ESLint, e deixe runes e IA lidarem com o scaffolding repetitivo enquanto você se concentra em entregar uma experiência de usuário polida.