Skip to content

Getting Started with Next.js

A live example is available at https://nextjs.nullstone.dev.

This quickstart launches a Next.js web application to AWS via Nullstone. It also configures a local development environment using Docker that works identical to production with debugging enabled.

This quickstart contains a walkthrough for creating a Next.js app. A working example is available to fork at nullstone-io/nextjs-quickstart.

TIP

This quickstart is based off Next.js Getting Started.

Create Next.js app

Initialize app

Next.js provides a template for new next.js applications. Run the following command to generate the starter template.

shell
yarn create next-app
yarn create next-app

Configure yarn commands

Open package.json and add the following scripts section:

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

Prepare for Local

The local setup utilizes docker to mimic how the app will work in production. However, the app is configured with hot reloading, debugging enabled, and cached node modules.

Configure docker locally

TIP

Nullstone provides a docker image nullstone/node:local that is configured for local development. The source for the docker image is on GitHub at nullstone-io/docker-node.

Create a docker-compose.yml to run locally using Docker.

yaml
version: '3.8'

services:
   app:
      image: nullstone/node:local
      command: ["yarn", "dev"]
      ports:
         - "3000:3000"
      environment:
         - PORT=3000
      volumes:
         - node_modules:/app/node_modules
         - next-cache:/app/.next
         - .:/app

volumes:
   node_modules: {}
   next-cache: {}
version: '3.8'

services:
   app:
      image: nullstone/node:local
      command: ["yarn", "dev"]
      ports:
         - "3000:3000"
      environment:
         - PORT=3000
      volumes:
         - node_modules:/app/node_modules
         - next-cache:/app/.next
         - .:/app

volumes:
   node_modules: {}
   next-cache: {}

Add a .dockerignore with the following:

docker
node_modules/
.next
.git
node_modules/
.next
.git

Let's start our app locally.

shell
docker compose up
docker compose up

Visit http://localhost:3000.

Update dependencies

Updating dependencies works identical without docker -- use yarn like normal. Then, restart your docker container with docker compose up or docker compose restart. The setup in docker-compose.yml caches node_modules and installs/synchronizes dependencies on boot.

Prepare for Production

To deploy the Next.js application to production, we need to dockerize the app. In this section, we will create a Dockerfile that will produce a docker image containing production-optimized assets.

Configure standalone mode

Open next.config.js and configure for standalone mode.

json
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  output: 'standalone'
}

module.exports = nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  output: 'standalone'
}

module.exports = nextConfig

Create Dockerfile

In your project root, create Dockerfile using the nullstone/node base image.

docker
# Install dependencies only when needed
FROM node:18-alpine AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

# Rebuild the source code only when needed
COPY . .
RUN yarn build

# Production image, copy all the files and run next
FROM nullstone/node

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --chown=nextjs:nodejs *.js ./

COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

# Disable telemetry during runtime.
ENV PORT 3000
EXPOSE 3000

CMD ["node", "server.js"]
# Install dependencies only when needed
FROM node:18-alpine AS builder
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

# Rebuild the source code only when needed
COPY . .
RUN yarn build

# Production image, copy all the files and run next
FROM nullstone/node

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --chown=nextjs:nodejs *.js ./

COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

# Disable telemetry during runtime.
ENV PORT 3000
EXPOSE 3000

CMD ["node", "server.js"]

Launch to Nullstone

Create app

When launching to Nullstone, we're going to create an app in the Nullstone UI and attach capabilities that automatically configure our app. Follow these steps in the Nullstone UI.

  1. Create an application.
    • Name: In this example, we're naming our app nextjs-quickstart
    • Framework: nextjs
    • App Type: Container
  2. From the Domains tab for the application, add a subdomain. (This will automatically attach a load balancer to your application)

Provision

Our application is ready to launch. Click "Launch" through the UI or issue up through the CLI.

shell
nullstone up --wait --app=nextjs-quickstart --env=dev --var=port=3000
nullstone up --wait --app=nextjs-quickstart --env=dev --var=port=3000

Build

Once your application is provisioned, you may build and deploy your app.

You can name your image whatever you want, just remember this image name for the deploy step. In this example, we are using an image name of nextjs.

shell
docker build -t nextjs .
docker build -t nextjs .

Deploy

Now, issue launch to push your docker image and deploy the service with a new version.

shell
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev

Troubleshooting

Auto-versioning

When pushing your image, Nullstone performs auto-versioning if you are in a git-tracked directory. Nullstone selects the short commit SHA (a unique 8-character token) from the git repository to tag the docker image.

To use a manual version, issue launch with --version (this example uses 1.0.0).

shell
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev --version=1.0.0
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev --version=1.0.0

Version conflicts

Nullstone enforces version/image tag immutability for security reasons.

If you repeatedly push a new docker image without committing anything to git, you will receive an error message like this:

shell
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.
error pushing artifact: error pushing image: tag invalid: The image tag 'c3c7cd83' already exists in the 'periwinkle-louse-fkslv' repository and cannot be overwritten because the repository is immutable.

The easiest way to resolve this is to launch with an indexed version. The following uses the same commit sha, but with a -2 suffix to distinguish the image tag.

shell
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev --version=c3c7cd83-2
nullstone launch --source=nextjs --app=nextjs-quickstart --env=dev --version=c3c7cd83-2