Amélioration progressive avec la propriété Action des formulaires
Reliez les formulaires aux Server Actions afin qu’ils fonctionnent sans JavaScript et s’enrichissent une fois l’hydratation terminée.
Amélioration progressive avec la propriété Action des formulaires est une leçon Next.js 15 Fullstack (App Router + Server Actions) gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Next.js 15 Fullstack (App Router + Server Actions), et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.
Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.
Forms That Work Without JavaScript
Progressive enhancement means a form works using plain HTML first, then gets better once JavaScript loads and React hydrates.
In Next.js 15 App Router, you achieve this by passing a Server Action directly to a form's action prop. The browser can submit the form natively to the server before any client JS arrives.
- No JS yet? The form still posts and the server responds.
- JS hydrated? React intercepts the submit and uses
fetchunder the hood, avoiding a full page reload.
What a Server Action Looks Like
A Server Action is an async function marked with the 'use server' directive. It runs only on the server.
When passed to <form action={...}>, Next.js wires it up so the form's fields arrive as FormData.
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const body = formData.get('body') as string
// ...persist to your database here
console.log('Saving:', { title, body })
}Wiring the Action to the Form
Pass the Server Action straight into the action prop. Because this is real HTML, each input needs a name attribute so it shows up in FormData.
This component can be a Server Component — no 'use client' needed for the basic case.
// app/new/page.tsx
import { createPost } from '../actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="body" placeholder="Body" />
<button type="submit">Publish</button>
</form>
)
}Why the name Attribute Matters
Native form submission serializes inputs by their name. Next.js builds the FormData object from those names — both when JS is off (native POST) and when it is on (intercepted fetch).
formData.get('title')reads the input namedtitle.- An input without a
nameis invisible to the server. - This is the same contract as classic HTML forms, which is exactly why it degrades gracefully.
The No-JavaScript Path
Disable JavaScript in your browser and submit the form. It still works.
Here is what happens on that path:
- The browser performs a standard HTTP POST to the current route.
- Next.js runs your Server Action on the server with the submitted
FormData. - If the action calls
redirect()orrevalidatePath(), the server responds with the updated page.
This is the baseline that progressive enhancement guarantees.
Redirect and Revalidate After Submit
After a successful mutation you usually want to refresh cached data and navigate. Both work on the no-JS path and the hydrated path.
revalidatePath('/posts')tells Next.js to refetch that route's data.redirect('/posts')sends the user to the new location.
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
await savePost({ title })
revalidatePath('/posts')
redirect('/posts')
}Validating FormData on the Server
Never trust client input. Validate inside the Server Action so the rules apply even when JavaScript is disabled.
A clean approach is to parse FormData with a schema and return a typed result the form can display.
// app/actions.ts
'use server'
import { z } from 'zod'
const schema = z.object({
title: z.string().min(3, 'Title too short'),
})
export async function createPost(formData: FormData) {
const parsed = schema.safeParse({
title: formData.get('title'),
})
if (!parsed.success) {
return { error: parsed.error.flatten().fieldErrors }
}
await savePost(parsed.data)
return { error: null }
}Enhancing with useFormStatus
Once hydrated, you can show pending UI. The useFormStatus hook reports whether the parent form is submitting.
It must be called from a child component of the form, inside a Client Component. When JS is off, this code simply doesn't run, and the plain button is what the user sees.
// app/submit-button.tsx
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Publishing...' : 'Publish'}
</button>
)
}Returning State with useActionState
To surface validation errors and success messages, wrap the action with useActionState (React 19 / Next.js 15). It threads the action's return value back into your component as state.
The form still works without JS: the same action runs on a native POST, just without the in-place state update.
// app/post-form.tsx
'use client'
import { useActionState } from 'react'
import { createPost } from './actions'
export function PostForm() {
const [state, formAction] = useActionState(createPost, { error: null })
return (
<form action={formAction}>
<input name="title" />
{state.error?.title && <p>{state.error.title[0]}</p>}
<button type="submit">Publish</button>
</form>
)
}Matching the Action Signature
When you use useActionState, the Server Action gains an extra first argument: the previous state. The signature changes from (formData) to (prevState, formData).
Keep the return shape consistent so the initial state and every result line up.
// app/actions.ts
'use server'
type State = { error: string | null }
export async function createPost(
prevState: State,
formData: FormData,
): Promise<State> {
const title = formData.get('title') as string
if (!title) return { error: 'Title is required' }
await savePost({ title })
return { error: null }
}Keeping the No-JS Path Healthy
Progressive enhancement only holds if you avoid breaking the native path. Watch out for:
- Relying on
onClickoronChangehandlers for required behavior — those need JS. - Submitting without a
type="submit"button. - Reading values from React state instead of
FormDatain the action.
Rule of thumb: the action and inputs carry the contract; client hooks only enhance it. Test by disabling JS at least once.
Quick Check
Test your understanding of how a Server Action enables progressive enhancement.
Recap
You learned how to build progressively enhanced forms in Next.js 15:
- Pass a Server Action to
<form action={...}>so it works via native POST before hydration. - Give every input a
name— that is the contract that feedsFormDataon both paths. - Validate on the server so rules apply even with JS disabled.
- Use
redirectandrevalidatePathto update the UI after a mutation. - Enhance with
useFormStatus(pending UI) anduseActionState(returned state), remembering the action signature becomes(prevState, formData). - Keep required behavior in the action and inputs, not in client-only handlers, and test with JavaScript off.
Apprends TypeScript avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 22
- Leçons
- 88
Questions Fréquemment Posées
La leçon « Amélioration progressive avec la propriété Action des formulaires » est-elle gratuite ?
Oui — le texte complet de « Amélioration progressive avec la propriété Action des formulaires » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Next.js 15 Fullstack (App Router + Server Actions), passe à CoddyKit PRO. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Amélioration progressive avec la propriété Action des formulaires » ?
Reliez les formulaires aux Server Actions afin qu’ils fonctionnent sans JavaScript et s’enrichissent une fois l’hydratation terminée. Tu pratiques Next.js 15 Fullstack (App Router + Server Actions) avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Next.js 15 Fullstack (App Router + Server Actions) ?
Aucune expérience préalable n'est requise. Next.js 15 Fullstack (App Router + Server Actions) sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.
Combien de temps prend la leçon « Amélioration progressive avec la propriété Action des formulaires » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Next.js 15 Fullstack (App Router + Server Actions) ?
Oui. Chaque leçon Next.js 15 Fullstack (App Router + Server Actions) inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Amélioration progressive avec la propriété Action des formulaires
- États d’attente et de chargement avec useFormStatus
- Erreurs de validation au niveau des champs avec useActionState
- Retour instantané avec useOptimistic