Standalone Next.js. When serverless is not an option

Focus Reactive - Apr 12 - - Dev Community

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',
    ...
};
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .