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.

// next.config.js
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.

"The default Next.js Image component needs a server to optimize images on the fly — static export doesn't have one, so it has to be turned off explicitly."

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.

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.