Most Next.js deployment guides assume you have a Node server, or that you're deploying to Vercel. A lot of the clients I build for don't have either — they have ordinary cPanel shared hosting, the kind that only serves static files. Getting a modern Next.js app to run well there means giving up a few defaults, but it's very doable once you know which knobs to turn.
Start with output: 'export'
The whole approach hinges on Next.js's static export mode. It compiles your app down to plain HTML, CSS, and JS — no server process required at all, which is exactly what shared hosting expects.
module.exports = {
output: 'export',
images: { unoptimized: true },
trailingSlash: true
}
That last line matters more than it looks. Apache on shared hosting resolves /about differently than /about/, and without trailingSlash: true you'll get inconsistent 404s depending on how a link was written.
Images: give up automatic optimization
The Image component's on-demand resizing and format conversion relies on a running server. With unoptimized: true, Next.js just outputs the image as-is at build time. For the multi-theme storefronts I build, I compensate by pre-sizing and compressing product images before they ever reach the project — the optimization happens once, at upload time, instead of per-request.
Dynamic routes need generateStaticParams
Every dynamic route — a product page, a blog post — has to be enumerable at build time, since there's no server left to resolve unknown paths after deployment. That means every [slug] route needs a generateStaticParams function that returns the full list of valid paths up front.
- Any route you can't enumerate at build time can't be a dynamic route under static export.
- API routes don't exist in the output — anything that needs a server goes through an external API instead (usually the Laravel backend).
- Client-side data fetching (SWR, plain
fetch) still works fine — it's server-side rendering that's off the table, not client interactivity.
Routing on Apache
Static export produces a folder of HTML files, and Apache needs a small .htaccess to route cleanly — otherwise a hard refresh on a nested page can 404. A simple rewrite rule that falls back to the matching HTML file (or a custom 404 page) covers almost every case I've run into.
None of this makes the app feel any less like Next.js day to day — routing, layouts, and client components all work the same way you'd expect. It just means the final build target is "a folder of files an Apache server can hand out," instead of a Node process. Once that trade-off clicks, static export stops feeling like a limitation and starts feeling like the simplest possible deployment story.
