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
- load function: the SvelteKit server hook that runs before the page renders and fetches the initial data.
- invalidate: reruns a
loadresult 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 basic tool that turns that contract into real requests is the Fetch API. 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. The action reads inputs via FormData before turning them into JSON for the API.
// 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>
💬 댓글
이 글에 대한 의견을 남겨주세요