SvelteKit Form Actions: Progressive Enhancement POST
- Last updated
- Prerequisites:
- SvelteKit routing and load functions
- Understanding of HTML forms and POST
- sveltekit
- form-actions
- progressive-enhancement
- forms
- use-form
- server
Read at your depth
The practical view
SvelteKit form actions handle form submissions on the server. In +page.server.ts: export const actions = { default: async ({ request }) => { const data = await request.formData(); return { success: true }; } }. In the page: <form method="POST" action="?/default"> or just <form method="POST">. Use the use:enhance action to progressively enhance: it submits via fetch, updates the page with the returned result, and handles loading/validation without a full reload. The returned action data is available as form in the page. This is the standard way to do mutations in SvelteKit.
The same idea in other frameworks
Next.js Server Actions / React Router actions are the framework-level analogues.Angular has no built-in form action primitive; services + reactive forms fill the role.Nuxt's useFormActions / server routes approximate the same pattern.SvelteKit form actions — the current topic.Legacy vs modern
Client-only fetch handler vs progressive form action
Hand-rolled client-side submission breaks without JS and duplicates validation; a form action serves both the no-JS POST and the enhanced fetch path from one definition.
async function onSubmit(e) {
e.preventDefault(); // ❌ OLD: blocks native submission entirely
await fetch('/api/login', { method: 'POST', body: new FormData(e.target) });
}export const actions = {
default: async ({ request }) => {
const fd = await request.formData();
return { ok: true }; // ✅ NEW: server handles submission
},
};Interview gotchas
Context
This tests the progressive-enhancement contract — interviewers want to confirm you understand that form actions are genuinely server endpoints that work without JS, not fetch-only APIs.
The mechanical answer
A form action is a server route handler for POST requests to the page URL (with the ?/action-name query when named). Without JavaScript, the browser performs a native form POST: it serializes the form fields, submits to the page URL, and the SvelteKit server runs the matching action, re-runs the load functions, and returns the full re-rendered HTML page. The browser renders that response as a normal page load — no JS required. The action's return value is available to the page via the form prop, which SvelteKit serializes into the HTML. This means validation, mutation, and error display all work server-side with zero client script. use:enhance only adds the enhanced fetch path on top; it is strictly additive.
Trap
The naive answer is 'form actions are like API routes — they need fetch'. They are server endpoints that also support native browser POST. Another trap: assuming the action result is automatically applied without reload in no-JS mode — the whole page re-renders, which is correct behavior, not a failure. Candidates who explain the dual path (native POST vs fetch) show real understanding.
Context
This probes the enhancement mechanics — the fetch interception and the invalidate-and-update flow that makes the SPA-like UX work while preserving the no-JS path.
The mechanical answer
use:enhance is an action (the SvelteKit built-in enhance) applied to a form. It intercepts the submit event, prevents the native navigation, serializes the form data, and fetches the page URL with POST (including ?/action for named actions). The server runs the action and re-runs load functions; the response contains the action result and the updated data. SvelteKit then updates the page: it re-renders the affected data (via the returned update callback you can customize), populates the form prop with the action result, and resets or keeps the form per your options. The enhancement also manages pending state (submitting) and can call invalidate() to refresh specific data. If the fetch fails, it falls back to the native submission so the form still works.
Trap
The naive answer is 'use:enhance just makes a fetch call'. It coordinates the full update cycle: action result application, load invalidation, pending state, and the fallback path. Another trap: forgetting that the action result flows through the form prop — if your page doesn't read form, error messages silently vanish. Candidates who describe the fetch → invalidate → apply pipeline show real SvelteKit experience.