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.
- Server functions may encounter performance issues due to Lambda cold starts, impacting user response time. Some providers have minimal delays (like Cloudflare Workers due to V8 engine), while others require additional steps. For instance, one way to mitigate this is by periodically invoking the server function, although this would require investigation and potential costs.
- Serverless functions are not ideal for long-running or computationally intensive tasks due to timeouts and billing based on execution time and/or memory usage.
- Serverless architectures offer less granular control over the underlying infrastructure compared to serverful environments.
- Testing and debugging code locally can be more tricky compared to traditional server environments. It can be difficult to perfectly replicate the serverless environment, making it hard to predict exactly how your code will behave in production.
- Some serverless providers may not support all the latest Next.js features.
These are just some of the things to consider when choosing the right approach. Remember, the ideal solution depends on your specific project requirements.
IT'S TIME FOR SERVERFUL
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:
- The deployment and configuration processes may not be as straightforward as the "one-click deployment" feature offered by serverless platforms, but it's still manageable.
- Servers typically incur higher costs as they run continuously regardless of traffic, unlike serverless pay-per-use models.
- Unlike serverless approaches, serverful environments may require more manual configuration, e.g. in terms of scaling (strategies, memory / CPU limits, load balancing, and other parameters).
CONCLUSION
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.