Skip to main content

Deploying Next.js to IONOS Deploy Now: Fixing Build Errors & SSG Config

 You have likely arrived here because your Next.js application runs perfectly on localhost, passes linting, and perhaps even deploys successfully to Vercel. However, when you push to the GitHub repository connected to IONOS Deploy Now, the pipeline fails.

The error logs in GitHub Actions are often opaque, referencing missing directories, failed distinct build steps, or 404 errors on your assets after a "successful" deployment.

This is a configuration mismatch between the default behavior of Next.js (Server-Side Rendering/Node.js runtime) and the infrastructure of IONOS Deploy Now, which frequently defaults to serving static assets via Apache/Nginx. This guide covers the root cause and the specific code changes required to stabilize your deployment pipeline.

The Root Cause: SSR vs. Static Export

By default, running npm run build in Next.js produces a hybrid application intended to run on a Node.js server. It creates a .next folder containing server logic, middleware, and render functions.

IONOS Deploy Now (depending on your specific plan and configuration) often expects a Static Site Generation (SSG) output. It looks for a folder of pure HTML, CSS, and JavaScript files to serve immediately. It does not spin up a Node.js process to handle requests dynamically.

When the GitHub Action runs, it builds the app but generates the .next server folder. IONOS then attempts to upload a static dist or build folder, finding it empty or non-existent, crashing the pipeline.

To fix this, we must force Next.js into Static Export mode and align the output directories.

Step 1: Configuring next.config.mjs

We need to tell the Next.js compiler to output raw HTML files instead of Node.js server functions. We also need to handle image optimization, as the default Next.js Image component relies on an on-demand server-side optimizer that doesn't exist in a static environment.

Open your next.config.mjs (or .js) and modify it as follows:

/** @type {import('next').NextConfig} */
const nextConfig = {
  // 1. Force a static export.
  // This generates an 'out' folder containing the HTML/CSS/JS assets.
  output: 'export',

  // 2. Disable server-side image optimization.
  // Without this, builds fail because <Image> expects an optimization API.
  images: {
    unoptimized: true,
  },

  // Optional: distinct distDir if IONOS specifically demands 'dist'
  // distDir: 'dist', 
};

export default nextConfig;

Why this works

  • output: 'export': This instructs Next.js to run next build and immediately follow it with an export routine. It generates an out folder by default.
  • images.unoptimized: The default Next.js <Image /> component calls a robust optimization API at runtime. Since we are deploying static files, there is no server to optimize images on the fly. Setting this to true serves images as-is.

Step 2: Aligning the Output Directory

By default, Next.js exports to a folder named out. However, IONOS Deploy Now (and many other CI/CD tools) creates a workflow that often defaults to looking for a dist or build folder.

You have two options here.

Option A: Change Next.js Output (Recommended)

Modify next.config.mjs to output directly to dist, which is a standard convention for IONOS.

const nextConfig = {
  output: 'export',
  distDir: 'dist', // Changes output from 'out' to 'dist'
  images: { unoptimized: true },
};

Option B: Configure IONOS Project Settings

If you prefer keeping the out folder, you must update the IONOS configuration.

  1. Log in to the IONOS Deploy Now dashboard.
  2. Navigate to your project settings.
  3. Locate Publish Directory or Dist Folder.
  4. Change the value from / or dist to out.

Step 3: Handling Dynamic Routes (App Router)

If you are using the App Router (app/) and have dynamic segments like app/blog/[slug]/page.tsx, the build will now fail.

In a server environment, Next.js generates these pages on request. in a Static Export, Next.js must know every possible path at build time.

You must implement generateStaticParams.

File: app/blog/[slug]/page.tsx

import { notFound } from 'next/navigation';

// Mock data fetcher - replace with your CMS or Database call
async function getPosts() {
  return [
    { slug: 'deploying-nextjs' },
    { slug: 'react-server-components' },
    { slug: 'github-actions-guide' },
  ];
}

// CRITICAL: This function tells Next.js which HTML files to build
export async function generateStaticParams() {
  const posts = await getPosts();
  
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const { slug } = params;
  
  // Logic to fetch specific post content
  if (!slug) return notFound();

  return (
    <article className="prose lg:prose-xl mx-auto py-10">
      <h1>Dynamic Post: {slug}</h1>
      <p>This page was statically generated at build time.</p>
    </article>
  );
}

Without generateStaticParams, the compiler cannot determine which HTML files to create for [slug], causing a build error stating that the page requires server-side rendering.

Step 4: The GitHub Actions Workflow

IONOS Deploy Now creates a .github/workflows/deploy-now.yaml file in your repository. You typically do not need to edit this manually if you correctly configured Step 1 and Step 2.

However, verify the build command in your package.json. IONOS executes the build script defined here.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build", 
    "start": "next start",
    "lint": "next lint"
  }
}

Note: Do not change "build": "next build" to "next build && next export". The next export command is deprecated in favor of the output: 'export' config in Step 1.

Pitfalls & Edge Cases

API Routes Support

If your application relies on pages/api or Route Handlers (app/api/route.ts), these will not work in a static export deployment on standard hosting. Static hosting serves files; it does not execute backend code.

  • Fix: Move backend logic to a separate backend service, serverless functions (like AWS Lambda), or ensure your Route Handlers return static data using GET methods purely for build-time generation.

Middleware Limitations

Middleware (middleware.ts) in Next.js relies on the Edge Runtime. This is generally incompatible with output: 'export' because there is no request-interception layer on a static file server.

  • Fix: Remove middleware or refactor logic into client-side checks or layout wrappers.

Client-Side Navigation

Ensure you are using the Next.js <Link> component. Regular <a> tags trigger a full browser refresh. In a static export deployed to a subfolder (rare, but possible), regular anchors might misinterpret the base path. Next.js <Link> handles routing internally, preserving the Single Page Application (SPA) feel.

Verification

To verify your fix before pushing to GitHub:

  1. Run npm run build locally.
  2. Check your project root.
    • If you set distDir: 'dist', ensure a dist folder exists containing index.html.
    • If you left it default, ensure an out folder exists.
  3. Install a local static server to test: npx serve@latest out (or dist).
  4. Navigate the site locally. If images load and pages navigate without server errors, your IONOS deployment is ready.

By strictly defining the build output and ensuring your dynamic routes are statically analyzable, you convert Next.js from a Node.js application into a robust static asset bundle compatible with IONOS Deploy Now's infrastructure.