Progressive Enhancement: Forms That Work Without JavaScript

Why a Server Action form submits before JavaScript loads, how the browser posts it as plain HTML, and what changes in Client Components.

5 min read

Progressive enhancement forms in Next.js submit even before JavaScript loads. A Server Component form that points at a Server Action works as plain HTML, so the browser can post it without any client script. The visitor gets a working form immediately, and interactivity improves once JavaScript hydrates.

The action runs only on the server, so it can reach your database while the form still behaves like a normal HTML form. It returns nothing, because a plain form ignores whatever an action returns.

typescripttypescript
// app/actions.ts
'use server'
import { db } from '@/lib/db'
 
export async function subscribe(formData: FormData) {
  const email = String(formData.get('email') ?? '')
  if (!email.includes('@')) return
  await db.subscriber.create({ data: { email } })
}

Now attach it in a Server Component. The form element renders on the server, and the action prop points at the exported function.

App.tsxApp.tsx
// app/page.tsx
import { subscribe } from './actions'
export default function SubscribeForm() {
  return <form action={subscribe}>
    <label htmlFor="email">Email</label>
    <input id="email" name="email" required />
    <button type="submit">Subscribe</button>
  </form>
}

Without JavaScript, the browser posts the email field and the action runs on the server. The required attribute also works natively, so an empty submit is blocked before anything reaches the server.

What the browser does without JavaScript

A form with an action prop is real HTML. When the visitor submits, the browser encodes the fields as FormData and sends them, and Next.js runs the action before returning a rendered page. No React code needs to load for that basic round trip.

What the visitor sees is a normal form submit: the page reloads and shows the fresh result. This is the point of progressive enhancement: the core task works everywhere, and JavaScript only adds the extras on top.

The form never depends on hydration to be usable. That also means it keeps working on a flaky connection where the bundle fails to arrive, which is the case most forms quietly break on.

Client Components queue until hydration

A Client Component form that calls a Server Action behaves differently before hydration. Submissions are queued and prioritized for hydration, and after the browser hydrates, the form submits without a full page reload.

For a no-JavaScript submission in a Client Component, pass a permalink string as the third argument to useActionState. If the form is submitted before the bundle loads, the browser navigates to that URL instead of the current one.

React only knows how to carry the state across if the destination page renders the same form component, with the same action and the same permalink. Once the page is interactive, the argument has no effect.

What still needs JavaScript

Pending state, optimistic updates, and field errors shown after submission all need JavaScript. Without it, the visitor sees a full page reload and the server-rendered result instead, so the submission still works but feels slower.

You can test this by disabling JavaScript in the browser. The form still submits and the page still updates, but the button label never changes and no inline error appears until the reload returns.

Use the native required attribute for basic checks that run without JavaScript, and keep server-side validation as the real guard. See building forms in Next.js with Server Actions for the full pattern, and useActionState for pending state and server errors for the state lifecycle.

Rune AI

Rune AI

Key Insights

  • Point the form action prop at a Server Action.
  • A Server Component form submits before JavaScript loads.
  • Client Components queue submissions until hydration.
  • Native attributes like required work without JavaScript.
  • Layer pending and error UI on top, never as the only path.
RunePowered by Rune AI

Frequently Asked Questions

Does a Server Action form need JavaScript to submit?

No. A form with an action prop works as plain HTML, so the browser can post it before JavaScript loads or when JavaScript is disabled.

What happens in a Client Component before hydration?

Submissions are queued and prioritized for hydration. After hydration, the form submits without a full page reload.

What still requires JavaScript in a form?

Pending state, optimistic updates, and field errors shown after submission all need JavaScript. The native required attribute works without it.

Conclusion

Progressive enhancement means a form's core submit path works as plain HTML and improves once JavaScript hydrates. Keep the action prop on the form, use native attributes for basic checks, and layer the interactive extras on top.