Skip to content
    SeaCode
    Learning tracksCode ArenaWorkshops & EventsInstructors
    Log inSign up
    NNext.js

    Next.js foundations

    • What is Next.js?
    • The App Router: folders are routes
    • Dynamic routes
    • Client-side navigation

    Server, client and data

    • Server and Client Components
    • Fetching data on the server
    • loading, error and not-found
    • Caching and revalidation

    Mutations, APIs and security

    • Server Actions
    • Route Handlers: building an API
    • Proxy (formerly Middleware)
    • Authentication and protecting pages

    Going to production

    • Metadata and SEO
    • Images, fonts and performance
    • Environment variables and deploying to Vercel
    • Project: a course catalogue
    Next.js

    Mutations, APIs and security · Lesson 9 of 16

    Server Actions

    Server functions called from forms, validation, and useActionState.

    12 minutes2 games

    A Server Action is an async function marked "use server" that runs only on the server, yet can be called from a form or button on the client like a normal function. Next.js makes the request behind the scenes, so you don't need an API route for every form.

    TypeScript
    // app/settings/actions.ts
    "use server"
    import { revalidatePath } from "next/cache"
    import { createClient } from "@/utils/supabase/server"
    
    export async function saveBio(previous: { error?: string }, formData: FormData) {
      const supabase = await createClient()
      const { data: { user } } = await supabase.auth.getUser()
      if (!user) return { error: "Please sign in." }             // 1. who is calling?
    
      const bio = String(formData.get("bio") ?? "").trim()
      if (bio.length > 160) return { error: "Bio must be 160 characters or less." }   // 2. validate
    
      const { error } = await supabase.from("profiles").update({ bio }).eq("id", user.id)
      if (error) return { error: "Couldn't save. Try again." }
      revalidatePath("/settings")                                // 3. refresh the page data
      return {}
    }
    TypeScript
    // app/settings/bio-form.tsx
    "use client"
    import { useActionState } from "react"
    import { saveBio } from "./actions"
    
    export function BioForm({ bio }: { bio: string }) {
      const [state, formAction, pending] = useActionState(saveBio, {})
      return (
        <form action={formAction}>
          <textarea name="bio" defaultValue={bio} maxLength={160} />
          <button disabled={pending}>{pending ? "Saving…" : "Save"}</button>
          {state.error && <p role="alert">{state.error}</p>}
        </form>
      )
    }

    The form submits

    Next.js POSTs the FormData to the server

    saveBio: check user → validate → save → revalidate

    Response: new state + the page with fresh data

    Important

    Every server action is a public endpoint on the internet: anyone can call it with any data, even if the button is hidden in the UI. So inside every action: verify the user and their permissions, validate every input (type, length, allowed values), and never trust an id from the form without checking it belongs to this user.

    Spot the bug

    This action has a serious vulnerability. Which line?

    Interview question

    When do you use a Server Action versus a Route Handler?

    Exercise

    Write validateCourse(formData) returning an array of errors: title is required when the title is empty, and price must be 0 or more when the price is negative or not a number. Print the result for both forms (one error per line, or ok when there are none).

    Expected output:
    ok
    title is required
    price must be 0 or more
    JavaScript
    function validateCourse(formData) {
      const errors = []
      
      return errors
    }
    
    const good = new FormData()
    good.set("title", "React")
    good.set("price", "600")
    
    const bad = new FormData()
    bad.set("title", "  ")
    bad.set("price", "-5")
    
    for (const form of [good, bad]) {
      const errors = validateCourse(form)
      console.log(errors.length ? errors.join("\n") : "ok")
    }

    Learn more from

    • Next.js docs: Mutating data (Server Functions)nextjs.org
    • react.dev: Server Functionsreact.dev
    • react.dev: useActionStatereact.dev
    • Next.js blog: How to think about security in Next.jsnextjs.org

    Played 0 of 2 games

    Sign in to save your progress and earn XP.

    PreviousCaching and revalidationNextRoute Handlers: building an API
    SeaCode

    Learn to code for real, online or at our branch in front of Cairo University, Giza.

    Learn

    • All courses
    • Interactive tutorials
    • Interview practice problems
    • Mock interview
    • Learning tracks
    • Code Arena
    • Workshops & Events
    • Knowledge library
    • Team projects

    Your account

    • Log in
    • Sign up
    • Learning plan
    • Certificates
    • Site map

    Contact us

    • WhatsApp
    • 010 9094 1104
    • In front of Cairo University, Giza

    © 2026 SeaCode. All rights reserved.