Standalone Next.js. When serverless is not an option
Ready to deploy your Next.js app, but unsure if serverless is the way to go? Let's explore both serverless and serverful approaches to help you choose the right path for your project.
Ready to deploy your Next.js app, but unsure if serverless is the way to go? Let's explore both serverless and serverful approaches to help you choose the right path for your project.
This article is part of the in-depth series about self-hosted Next.js and the challenges around it.
Serverless architecture comes with numerous significant advantages, yet it is not without its drawbacks, which can sometimes influence the choice of one approach over another.
These are just some of the things to consider when choosing the right approach. Remember, the ideal solution depends on your specific project requirements.
With a serverful approach, you can avoid these drawbacks, and the main challenge lies in selecting the platform that aligns with your requirements. Options may include AWS, Render, DigitalOcean, and others. While VPS is also an option, it's generally not recommended due to the significant setup and maintenance overhead involved (logging, monitoring, CI/CD pipelines, etc.). However, you can make your life easier by leveraging tools like Coolify that help managing your VPS.
In the case of Next.js, deploying it on any serverful platform is straightforward and involves no code changes. Essentially, you'll end up with a Node.js server. However, you may consider a minor update in your next.config.js
file to significantly reduce bundle size:
/** @type {import('next').NextConfig} */
const nextConfig = {
...
output: 'standalone',
...
};
Next.js keeps track of all the pages and their dependencies that are needed to run your application. With the config above necessary for your production deployment files will be automatically copied to standalone
folder inside your .next
, which you can deploy independently (refer to docs for more details). In the simplest scenario, your Dockerfile might look like the following:
FROM node:16-alpine AS base
FROM base AS deps
# 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
COPY package.json package-lock.json* ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME localhost
CMD ["node", "server.js"]
Depending on your chosen serverful platform, a Dockerfile might not always be necessary. Some platforms might use simpler bash commands for the build and deploy stages.
A serverful approach with Next.js might offer broader support for various features, independent of the specific provider you choose. There might still be minor nuances depending on the provider, but overall, you'll likely have more flexibility compared to some serverless limitations. For example, let's have a look at the same project deployed to both serverless (Vercel, AWS Amplify) and serverful (AWS ECS) environments:
Feature | Vercel | Amplify | ECS |
---|---|---|---|
Layout nested | ✅ | ✅ | ✅ |
Layout parallel | ✅ | ✅ | ✅ |
Layout error | ✅ | ✅ | ✅ |
Data fetching revalidate 10 | ✅ | ✅ | ✅ |
Data fetching revalidate 60 | ✅ | ✅ | ✅ |
Data fetching revalidate on demand | ✅ | ❌ | ✅ |
Data fetching no cache (no store) | ✅ | ✅ | ✅ |
Data fetching POST default | ✅ | ✅ | ✅ |
Data fetching POST without cache | ✅ | ✅ | ✅ |
Routing default | ✅ | ✅ | ✅ |
Routing no cache | ✅ | ✅ | ✅ |
Static metadata | ✅ | ✅ | ✅ |
Dynamic metadata SSG | ✅ | ✅ | ✅ |
Dynamic metadata SSR | ✅ | ✅ | ✅ |
JSON-LD | ✅ | ✅ | ✅ |
Image generation | ✅ | ❌ (HTTP 500) | ✅ |
Sitemap + robots.txt | ✅ | ✅ | ✅ |
Draft mode | ✅ | ❌ | ❌ |
RSC | ✅ | ✅ | ✅ |
Server Actions | ✅ | ✅ | ✅ |
Intercepting routes | ✅ | ✅ | ✅ |
API Routes | ✅ | ✅ | ✅ |
Fonts | ✅ | ✅ | ✅ |
Beyond feature support, serverful deployments generally offer better performance due to the absence of cold starts and timeouts. Additionally, you benefit from faster SSR times based on your server hardware capabilities.
However, serverful deployments come with their own considerations:
Choosing between serverless and serverful architectures depends on your project's specific needs. Serverless offers ease of deployment and automatic scaling, but might have limitations in performance, resource control, and task suitability. Serverful deployments provide more control and flexibility but require additional considerations for deployment, cost, and configuration. By understanding the advantages and limitations of both approaches, you can make an informed decision for your Next.js application.