Shipixen Logo

Deploying to Static hosting services / Edge / GitHub Pages / S3 / Firebase etc.

Updated on

It is possible to deploy the app to any static hosting service. However, that may require additional configuration to support Next.js features like server-side rendering (SSR), incremental static regeneration (ISR), next/image, etc.

Of course, you might be able to host this at a much lower cost than a server-based hosting service. This could be a good choice for a blog or a small landing page.

Changes required for static hosting services

When deploying to static hosting services, you need to make some changes to your Shipixen/Next.js project to ensure you're not using server-side functions or features that require a server.

  1. Add output: 'export' and in { images: { unoptimized: true } } next.config.js. See static exports documentation for more information.
next.config.js
onst { withContentlayer } = require('@shipixen/next-contentlayer-module');

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

-// You might need to insert additional domains in script-src if you are using external services
-const ContentSecurityPolicy = `...`

-const securityHeaders = [...]

/**
 * @type {import('next/dist/next-server/server/config').NextConfig}
 **/
module.exports = () => {
  const plugins = [withContentlayer, withBundleAnalyzer];
  return plugins.reduce((acc, next) => next(acc), {
+   output: 'export',
    reactStrictMode: true,
    pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
    eslint: {
      dirs: ['app', 'components', 'layouts', 'scripts'],
    },
    images: {
+     unoptimized: true,
      remotePatterns: [
        {
          protocol: 'https',
          hostname: 'picsum.photos',
          port: '',
          pathname: '**/*',
        },
        {
          protocol: 'https',
          hostname: 'shipixen.com',
          port: '',
          pathname: '**/*',
        },
      ],
    },
-   async headers() {
-     return [
-       {
-         source: '/(.*)',
-         headers: securityHeaders,
-       },
-     ];
-   },
    webpack: (config, options) => {
      config.module.rules.push({
        test: /\.svg$/,
        use: ['@svgr/webpack'],
      });

      return config;
    },
  });
};

  1. Comment out headers() from next.config.js.

  2. Change components/shared/Image.tsx to use a standard <img> tag instead of next/image:

components/shared/Image.tsx
- import NextImage, { ImageProps } from 'next/image'
+ import { ImageProps } from 'next/image';

- const Image = ({ ...rest }: ImageProps) => <NextImage {...rest} />;
+ // @ts-ignore
+ const Image = ({ ...rest }: ImageProps) => <img {...rest} />

export default Image

Alternatively, to continue using next/image, you can use an alternative image optimization provider such as Imgix, Cloudinary or Akamai. See image optimization documentation for more details.

  1. Remove api folder and components which call the server-side function such as the Newsletter component. Not technically required and the site will build successfully, but the APIs cannot be used as they are server-side functions. If you are using a dynamic Open Graph image, you will also need to change it to a static one. In metadata.js, replace 'socialBanner' with a static image URL (see metadata docs)

  2. Run npm run build. The generated static content is in the out folder.

  3. Deploy the out folder to your hosting service of choice or run npx serve out to view the website locally.

👉 Important Static generation needs at least one blog post with a tag to successfully satisfy generateStaticParams(). This might change in the future, but for now if you see the error Error: Page "<path>" is missing "generateStaticParams()" so it cannot be used with "output: export" config., you need to add a blog post to the data folder. For example:

data/sample.mdx
---
title: Sample
tags:
 - sample
date: 2024-01-01
summary: A sample post
---

Deploy to Github Pages via Github Actions

Complete the steps 1 to 6 from Static hosting services above and then:

Specify the base path in next.config.js to match your repository name e.g. basePath: "/shipixen-example" (for github.com/shipixen/shipixen-example)

To automate deployment, you can use the following Github Actions workflow:

# Sample workflow for building and deploying a Next.js site to GitHub Pages
#
# To get started with Next.js see: https://nextjs.org/docs/getting-started
#
name: Deploy Next.js site to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ['test-gh-pages-static']

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: 'pages'
  cancel-in-progress: false

jobs:
  # Build job
  build:
    name: Build Shipixen GH pages
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Detect package manager
        id: detect-package-manager
        run: |
          if [ -f "${{ github.workspace }}/yarn.lock" ]; then
            echo "manager=yarn" >> $GITHUB_OUTPUT
            echo "command=install" >> $GITHUB_OUTPUT
            echo "runner=yarn" >> $GITHUB_OUTPUT
            exit 0
          elif [ -f "${{ github.workspace }}/package.json" ]; then
            echo "manager=npm" >> $GITHUB_OUTPUT
            echo "command=ci" >> $GITHUB_OUTPUT
            echo "runner=npx --no-install" >> $GITHUB_OUTPUT
            exit 0
          else
            echo "Unable to determine package manager"
            exit 1
          fi
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: ${{ steps.detect-package-manager.outputs.manager }}
      - name: Restore cache
        uses: actions/cache@v3
        with:
          path: |
            .next/cache
          # Generate a new cache whenever packages or source files change.
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
          # If source files changed but packages didn't, rebuild from a prior cache.
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
      - name: Build project assets
        run: ${{ steps.detect-package-manager.outputs.manager }} build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: ./out

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2