You don't always need to write an API endpoint, client-side fetch, and loading state just to submit a form. A Server Action connects a form's action directly to a server function, giving you progressive enhancement — the form can even submit before JavaScript finishes loading.
The mental model
A Server Function must be async and carry the use server directive. Treat it just like a server endpoint the browser can call, and re-check authentication, authorization, and validation inside the function itself. After a mutation, refresh data with updateTag, revalidateTag, or revalidatePath, and you can redirect to the detail page. Returning expected validation errors as readable state is better for the user than throwing a raw exception.
Let's build it together
// app/actions.ts
"use server";
import { updateTag } from "next/cache";
export async function createNote(formData: FormData) {
const title = String(formData.get("title") ?? "").trim();
if (title.length < 3) return { error: "ခေါင်းစဉ် အနည်းဆုံး ၃ လုံးရေးပါ" };
await db.note.create({ data: { title } });
updateTag("notes");
return { success: true };
}
// app/notes/new/page.tsx
<form action={createNote}>
<label htmlFor="title">ခေါင်းစဉ်</label>
<input id="title" name="title" required minLength={3} />
<button type="submit">သိမ်းမယ်</button>
</form>How the code works
createNote pulls the title from FormData and checks it's at least three characters. It saves to the database, then expires the notes tag so the new list shows up. The form component just passes the function to the action prop.
A valid title gets saved to the database, and the change shows up in the notes list immediately.5-Minute Try-It
Add a content textarea and check on the server that it's between 10 and 2000 characters.
Next.js — Mutating Data — Next.js