Next.js Server Actions Not Working With Static Export

Next.js Server Actions not working static export stops the build outright rather than failing quietly, which is at least honest — the error names Server Actions specifically, and the reason is structural rather than a bug to patch. Server Actions are functions that execute on a server when a form submits or a client calls them, and output: 'export' produces a folder of static files with nothing running behind them. There's no process left to receive that invocation at all.
Short answer: Server Actions require a live server to execute, which a static export never has by definition — the fix is replacing the Server Action with a client component that handles onSubmit directly and calls an external API instead, the same architectural split a static export needs for any other server-side logic. Watch specifically for Server Action code pulled in indirectly through a library import, since that trips up projects that never wrote one directly.

Why Server Actions Can't Exist in a Static Export
A direct discussion thread on this exact limitation confirms the error text plainly: "Server Actions are not supported with static export." Next.js's own documentation on Server Actions and mutating data describes what they're actually built to do — respond to a form submission or client call by running server-side code — which is exactly what a static export has nowhere to execute. The static exports guide itself lists Server Actions plainly among the features output: 'export' doesn't support. The mechanism is the same one behind API routes, dynamic server usage, and every other server-dependent feature covered elsewhere in this series — a Server Action needs a server process to actually execute the function body when it's called, and output: 'export' commits to producing only static HTML, CSS, and JavaScript with no server running afterward. There's no partial version of this that works; a Server Action with nothing to execute it isn't a degraded feature, it's a function that can never run.
(If you've been double-checking your 'use server' directive syntax for a typo — it's very likely fine. The directive isn't wrong. It's asking for something a static export structurally cannot provide, no matter how it's written.)
The Trap: A Server Action You Never Wrote
Here's the genuinely easy way to hit this error without ever calling a Server Action directly: some libraries include Server Action code as part of their own internal implementation, and importing that library pulls the Server Action code into your bundle whether or not you ever invoke one yourself. The build fails naming Server Actions, and searching your own codebase for 'use server' finds nothing — because the code responsible isn't yours.
1// Your code has no Server Action at all —
2// but importing this specific library might still pull one in internally
3import { SomeComponent } from 'some-cms-library';If this happens, the fix is auditing what a specific import actually brings in — checking the library's own source or changelog for Server Action usage — rather than continuing to search your own components for a directive that was never there.

The Fix: Client-Side Submission to an External API
The replacement pattern moves the mutation itself to a separately deployed backend, with the static frontend responsible only for collecting input and handling the response:
1// WRONG — a Server Action, which cannot exist in a static export
2'use server';
3
4export async function submitContactForm(formData: FormData) {
5 const email = formData.get('email');
6 await db.contacts.create({ email });
7}1// RIGHT — client-side submission to a genuinely separate backend
2'use client';
3
4export default function ContactForm() {
5 async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
6 event.preventDefault();
7 const formData = new FormData(event.currentTarget);
8
9 await fetch(`${process.env.NEXT_PUBLIC_API_URL}/contacts`, {
10 method: 'POST',
11 headers: { 'Content-Type': 'application/json' },
12 body: JSON.stringify({ email: formData.get('email') }),
13 });
14 }
15
16 return (
17 <form onSubmit={handleSubmit}>
18 <input name="email" type="email" required />
19 <button type="submit">Submit</button>
20 </form>
21 );
22}preventDefault() stops the browser's native form submission, and the mutation itself runs against whatever backend actually handles it — a NestJS or Express service deployed separately, exactly the split our API routes and static export guide covers for any other server-dependent feature a static export can't run itself.

When Static Export Genuinely Isn't the Right Fit
If Server Actions are woven throughout the application rather than being one occasional form, working around this limitation form-by-form is a sign the underlying architecture decision needs revisiting, not a sign to keep patching. Removing output: 'export' and deploying to a platform that runs an actual Next.js server is the more honest fix in that case — static export is an excellent choice for a mostly-static, content-heavy frontend, and a genuinely bad fit for an application built around server-side mutations as its core interaction model.
The Opinion Part
Here's the position worth stating plainly: hitting this error on one form is a five-minute fix — swap the Server Action for a client submission to an external API, done. Hitting it on every form across an entire application is a signal, not a series of individual bugs, and the signal is that static export was the wrong architectural choice made before anyone asked whether the app actually needed a server underneath it. The honest response to that signal is reconsidering the deployment model, not writing the same client-side workaround fifteen times and calling the architecture decision settled.
Conclusion
If Next.js Server Actions aren't working under static export, that's not a bug to patch — a Server Action requires a server, and output: 'export' guarantees there isn't one. Replace the Server Action with a client component handling onSubmit and calling a genuinely separate backend, check carefully for Server Action code pulled in indirectly through a library import if you never wrote one yourself, and reconsider static export entirely if Server Actions turn out to be central to how the app actually works rather than an occasional convenience.
If dynamic APIs like cookies() are a separate constraint on the same project, our dynamic server usage guide covers the broader list of what static export rules out beyond just Server Actions.
Move the mutation to where it can actually run, and let the form submit the way it's supposed to — quietly, reliably, to a backend that was always going to need to exist somewhere.
Frequently Asked Questions
Server Actions are functions that run on the server in response to a form submission or client-triggered call, which fundamentally requires a running server process to receive and execute them. output: export produces only static HTML, CSS, and JavaScript files with nothing running afterward, so there's no process available to receive a Server Action invocation at all — Next.js reports Server Actions are not supported with static export rather than silently producing a build that can't function.
Yes — this is a genuinely easy trap. Some libraries include Server Action code internally as part of their own implementation, and importing that library pulls the Server Action code into your bundle even if you never call one directly. The fix in that case is auditing what a specific import actually brings in, not searching your own code for a 'use server' directive that was never there.
A client component handling onSubmit directly, calling preventDefault to stop the native form submission, then making a fetch request to an external API endpoint — the same separate-backend architecture a static export needs for anything else requiring server-side logic. The mutation itself moves to wherever that backend is actually deployed, and the static frontend becomes purely responsible for collecting input and displaying the response.
If Server Actions are central to how the application works — not an occasional convenience but the actual architecture — removing output: export and deploying to a platform that runs a real Next.js server is the more honest fix than working around the limitation with client-side calls everywhere. Static export is the right choice for a content-heavy or mostly-static frontend; an app built around Server Actions throughout was probably never a great fit for it in the first place.
If a Server Action is only used for static data fetching rather than a genuine runtime mutation, that specific use case can sometimes be replaced with an API route configured with force-static, since that data is genuinely available at build time. Anything that needs to run in response to a real user action after the site is deployed doesn't have that option — it needs an actual server, full stop.
