0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · Ders

Form Action Prop'u ile Aşamalı Geliştirme

Formları Server Actions'a bağlayarak JavaScript olmadan çalışmalarını ve yüklendikten sonra geliştirilmiş özellikler sunmalarını sağlayın.

Form Action Prop'u ile Aşamalı Geliştirme, CoddyKit'te ücretsiz bir Next.js 15 Fullstack (App Router + Server Actions) dersidir. Bu, 4 dersinin 1. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Next.js 15 Fullstack (App Router + Server Actions) öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Next.js 15 Fullstack (App Router + Server Actions) kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

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 fetch under 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 named title.
  • An input without a name is 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() or revalidatePath(), 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 onClick or onChange handlers for required behavior — those need JS.
  • Submitting without a type="submit" button.
  • Reading values from React state instead of FormData in 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 feeds FormData on both paths.
  • Validate on the server so rules apply even with JS disabled.
  • Use redirect and revalidatePath to update the UI after a mutation.
  • Enhance with useFormStatus (pending UI) and useActionState (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.

Sıkça Sorulan Sorular

“Form Action Prop'u ile Aşamalı Geliştirme” dersi ücretsiz mi?

Evet — “Form Action Prop'u ile Aşamalı Geliştirme” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Next.js 15 Fullstack (App Router + Server Actions) kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Next.js 15 Fullstack (App Router + Server Actions) kursu toplamda 4 dersten oluşur.

“Form Action Prop'u ile Aşamalı Geliştirme” dersinde ne öğreneceğim?

Formları Server Actions'a bağlayarak JavaScript olmadan çalışmalarını ve yüklendikten sonra geliştirilmiş özellikler sunmalarını sağlayın. Next.js 15 Fullstack (App Router + Server Actions) ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Next.js 15 Fullstack (App Router + Server Actions) öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Next.js 15 Fullstack (App Router + Server Actions), başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 1. dersidir.

“Form Action Prop'u ile Aşamalı Geliştirme” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Next.js 15 Fullstack (App Router + Server Actions) dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Next.js 15 Fullstack (App Router + Server Actions) dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Form Action Prop'u ile Aşamalı Geliştirme
  2. useFormStatus ile Bekleme ve Yükleme Durumları
  3. useActionState ile Alan Düzeyinde Doğrulama Hataları
  4. useOptimistic ile Anında Geri Bildirim
← Next.js 15 Fullstack (App Router + Server Actions) Sayfasına Dön