You already have the form. Now wire it to the real server while keeping interactions snappy.
Quick recap
- What we're building: the
/projectspage that loads the project list from the server, creates entries, and toggles stars optimistically. - Why it matters: once you understand the
load/invalidatepairing you can sync server state without hard reloads, and optimistic UI keeps perceived speed high on slow networks. - Watch out for: line up
depends/invalidatekeys precisely, and capture snapshots before optimistic updates so failures can roll back instantly.
Key terms
loadfunction: the SvelteKit server hook that runs before the page renders and fetches the initial data.invalidate: reruns aloadresult for a specific key so the UI refreshes without reloading the full page.- Optimistic UI: renders the expected result before the server confirms it to boost perceived responsiveness.
- Snapshot rollback: cloning the list before an optimistic update so you can revert immediately on failure.
Core ideas
An API (application programming interface) is a contract for exchanging data with the server. The load function calls the API before rendering so the page has data ready. invalidate asks SvelteKit to rerun a load tied to a key. Optimistic UI paints the expected result first; to keep it safe you must hold a snapshot and restore it if the request fails.
Server vs client boundary
+page.server.tshostsloadandactions; they only run on the server and can touch the database or secrets.+page.svelterenders in the browser, receivesdatafrom the server, and can callinvalidateto rerun the serverload.- The optimistic toggle updates the browser store (
projectsStore) first. If the server rejects the change, it restores the snapshot. Remember you are intentionally doing “show first, confirm later.”
Data flow (D2)
👉 Mini recap: the browser form triggers the action, the action talks to the API, and on success it calls invalidate so the list refreshes without a full reload. The star toggle stores a snapshot so it can revert instantly when the server returns an error.
Code examples
The list fetches projects, creating entries refreshes the list, and the star toggle stays optimistic.
// src/routes/projects/+page.server.ts
export const load = async ({ fetch, depends }) => {
depends('data:projects');
const res = await fetch('/api/projects');
if (!res.ok) {
return { projects: [], error: 'Failed to load projects.' };
}
return { projects: await res.json() };
};
export const actions = {
create: async ({ request, fetch }) => {
const form = Object.fromEntries(await request.formData());
if (!form.title) {
return fail(400, { errors: { title: 'Title is required.' } });
}
const res = await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
});
if (!res.ok) {
return fail(500, { errors: { title: 'Failed to create project.' } });
}
return { created: true };
}
};
<!-- src/routes/projects/+page.svelte -->
<script>
import { enhance } from '$app/forms';
import { invalidate } from '$app/navigation';
import { page } from '$app/stores';
import { derived, writable, get } from 'svelte/store';
export let data;
const projectsStore = writable(data.projects);
const pending = derived(page, ($page) => $page.form?.errors);
async function toggleStar(id) {
const snapshot = get(projectsStore);
projectsStore.update((list) => list.map((p) => (p.id === id ? { ...p, starred: !p.starred } : p)));
const res = await fetch(`/api/projects/${id}/star`, { method: 'POST' });
if (!res.ok) projectsStore.set(snapshot);
}
</script>
<form method="POST" action="?/create"
use:enhance={({ result }) => {
if (result.type === 'success') invalidate('data:projects');
}}>
<label>
<span class="sr-only">Project name</span>
<input name="title" placeholder="Project name" aria-invalid={pending?.title ? 'true' : 'false'} required />
</label>
{#if pending?.title}
<p class="form-error">{pending.title}</p>
{/if}
<button type="submit">Add</button>
</form>
<ul>
{#each $projectsStore as project (project.id)}
<li>
<button type="button" on:click={() => toggleStar(project.id)} aria-pressed={project.starred}>
{project.starred ? '★' : '☆'}
</button>
{project.title}
</li>
{/each}
</ul>
💬 댓글
이 글에 대한 의견을 남겨주세요