Skip to main content

Scaling & High Availability

This guide covers strategies for scaling the Ever Works Template from a single-instance deployment to a highly available production setup, including serverless configuration, connection pooling, CDN optimization, and edge functions.

Deployment Architecture

The template supports multiple deployment architectures:

ArchitectureBest ForScaling Model
Vercel (Serverless)Most deploymentsAutomatic horizontal scaling
Docker (Standalone)Self-hosted, on-premiseManual or orchestrator-based scaling
Node.js (Direct)Development, simple deploymentsSingle instance or PM2 cluster

Serverless Configuration (Vercel)

Standalone Output

The template is configured with standalone output for optimal serverless deployment:

// next.config.ts
const nextConfig: NextConfig = {
output: "standalone",
};

Standalone mode produces a self-contained build in .next/standalone/ that includes only the files necessary to run the application. This minimizes cold start times by reducing the deployment package size.

Function Configuration

Configure serverless function settings in vercel.json or via route-level configuration:

// app/api/heavy-computation/route.ts
export const maxDuration = 60; // seconds (Pro plan: up to 300s)
export const dynamic = 'force-dynamic';
Route TypeMax DurationMemoryNotes
API routes (simple)10s1024 MBDefault for most endpoints
API routes (data processing)30s1024 MBFor batch operations
Cron jobs60s1024 MBBackground task execution
Webhook handlers30s1024 MBPayment, OAuth callbacks
Static pagesN/AN/APre-rendered at build time

Cold Start Optimization

Minimize cold starts with these techniques:

TechniqueImplementationImpact
Minimize function sizeserverExternalPackages in configReduces initialization time
Avoid top-level importsDynamic import() for heavy modulesDefers loading until needed
Use edge runtime where possibleexport const runtime = 'edge'Near-zero cold start
Warm functionsHealth check endpoints with monitoringKeeps functions active

Database Connection Pooling

The Problem

In serverless environments, each function invocation may open a new database connection. Without pooling, this can exhaust the database's connection limit.

Solution: Connection Pooler

Use a connection pooler between your application and database:

PoolerProviderSetup
PgBouncerSupabase (built-in)Use the pooled connection string (port 6543)
Neon PoolerNeon (built-in)Use the -pooler connection string
PgBouncerSelf-hostedDeploy PgBouncer alongside PostgreSQL

Configuration

Use different connection strings for pooled and direct connections:

# Pooled connection for application queries (serverless-safe)
DATABASE_URL=postgresql://user:pass@host:6543/db?pgbouncer=true

# Direct connection for migrations only
DIRECT_DATABASE_URL=postgresql://user:pass@host:5432/db

Update drizzle.config.ts to use the direct connection for migrations:

export default {
schema: "./lib/db/schema.ts",
out: "./lib/db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DIRECT_DATABASE_URL || process.env.DATABASE_URL,
},
} satisfies Config;

Connection Limits

TierMax ConnectionsRecommended Pool Size
Hobby (Neon/Supabase)50-10010-20
Pro (Neon/Supabase)200-50050-100
Enterprise1000+100-200

Connection Management in Code

The template's database module should reuse a single connection pool per function instance:

// lib/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

// Create connection pool once per serverless instance
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString, {
max: 10, // Maximum connections in pool
idle_timeout: 20, // Close idle connections after 20s
connect_timeout: 10,
});

export const db = drizzle(client);

CDN and Caching

Vercel Edge Network

When deployed to Vercel, the Edge Network automatically provides:

  • Global CDN distribution across 30+ regions
  • Automatic static asset caching
  • Edge caching for ISR (Incremental Static Regeneration) pages
  • DDoS protection

Cache Control Headers

Configure caching for different content types:

// API route with cache headers
export async function GET() {
const data = await fetchData();

return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}

Caching Strategy by Content Type

Content TypeCache StrategyTTLNotes
Static assets (JS, CSS, images)Immutable1 yearContent-hashed filenames
Public pagesISR60-300sRevalidate on demand
API responses (public)s-maxage10-60sCDN-level caching
API responses (authenticated)no-store0Never cache user-specific data
CMS content pagesISR300sRevalidate after content sync

ISR (Incremental Static Regeneration)

Use ISR for content-heavy pages that change infrequently:

// app/[locale]/discover/[page]/page.tsx
export const revalidate = 300; // Regenerate every 5 minutes

export default async function DiscoverPage({ params }) {
const items = await fetchItems(params.page);
return <ItemGrid items={items} />;
}

On-Demand Revalidation

Trigger revalidation after content updates:

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(request: Request) {
const { secret, path } = await request.json();

if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}

revalidatePath(path);
return Response.json({ revalidated: true });
}

Edge Functions

When to Use Edge Runtime

Edge functions run on Cloudflare Workers (via Vercel) and provide near-zero cold start times. Use them for:

Use CaseExample
Geolocation-based routingRedirect users to regional content
A/B testingRoute to experiment variants
Authentication checksQuick session validation
Response transformationAdd headers, modify responses
Simple API endpointsLightweight data lookups

Edge Runtime Limitations

LimitationDetail
No Node.js APIsCannot use fs, child_process, etc.
No native modulesCannot use bcryptjs, postgres directly
Limited execution time30 seconds max (Vercel Pro)
Limited memory128 MB
No Drizzle ORMUse edge-compatible database clients

Example Edge Function

// app/api/geo/route.ts
export const runtime = 'edge';

export async function GET(request: Request) {
const country = request.headers.get('x-vercel-ip-country') || 'US';
const city = request.headers.get('x-vercel-ip-city') || 'Unknown';

return Response.json({
country,
city,
timestamp: Date.now(),
});
}

Horizontal Scaling Strategies

Stateless Application Design

The template is designed to be stateless at the application layer:

ComponentState LocationScaling Impact
SessionsDatabase or JWTNo shared state between instances
Background jobsJob manager (per-instance or Trigger.dev)Use Trigger.dev for multi-instance
File uploadsExternal storage (S3, Supabase)No local filesystem dependency
CMS contentGit repository (cloned at build/startup)Read-only, identical per instance
CacheIn-memory (per-instance) or RedisConsider Redis for shared cache

Multi-Instance Considerations

When running multiple instances (Docker Swarm, Kubernetes, or multiple Vercel functions):

  1. Background jobs: Use Trigger.dev or Vercel Cron instead of the LocalJobManager to avoid duplicate executions.
  2. Database connections: Enable connection pooling to prevent connection exhaustion.
  3. Session storage: Use database sessions instead of in-memory stores.
  4. Cache invalidation: Implement a shared cache (Redis) or accept eventual consistency with per-instance caches.

Monitoring at Scale

Key Metrics to Track

MetricToolThreshold
Response time (p95)Sentry, Vercel Analytics< 500ms
Error rateSentry< 1%
Database connection countDatabase dashboard< 80% of max
Function cold startsVercel AnalyticsMonitor frequency
Cache hit rateApplication logs> 80%
Memory usageVercel/Docker metrics< 80% of limit

Sentry Performance Monitoring

The template configures Sentry with trace sampling:

Sentry.init({
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
});

Adjust tracesSampleRate based on traffic volume:

Daily RequestsRecommended Sample Rate
< 10,0001.0 (100%)
10,000-100,0000.1 (10%)
100,000-1,000,0000.01 (1%)
> 1,000,0000.001 (0.1%)

Load Testing

ToolUse CaseComplexity
autocannonQuick HTTP benchmarksLow
k6Scripted load testsMedium
ArtilleryComplex scenariosMedium
LocustPython-based, distributedHigh

Example Load Test

# Quick benchmark with autocannon
npx autocannon -c 50 -d 30 https://your-app.vercel.app/api/health

# k6 script for more detailed testing
k6 run load-test.js

Testing Checklist

TestTargetPass Criteria
Homepage load100 concurrent usersp95 < 1s
API endpoint200 requests/secondp95 < 500ms, 0% errors
Search query50 concurrent usersp95 < 2s
Auth flow20 concurrent usersAll succeed, no timeouts

Scaling Checklist

CategoryItemPriority
DatabaseEnable connection poolingCritical
DatabaseUse read replicas for heavy read loadsHigh
DatabaseAdd indexes for slow queriesHigh
CachingConfigure CDN caching headersCritical
CachingImplement ISR for content pagesHigh
CachingAdd Redis for shared cache (if multi-instance)Medium
ComputeUse edge runtime for lightweight routesMedium
ComputeOptimize cold starts with external packagesHigh
JobsSwitch to Trigger.dev for multi-instanceHigh
JobsConfigure Vercel Cron for scheduled tasksHigh
MonitoringSet up Sentry with appropriate samplingCritical
MonitoringConfigure alerts for error rate and latencyHigh
TestingRun load tests before major launchesHigh