@siddharatha/adapter-node-rolldown
v1.0.8
Published
High-performance SvelteKit adapter for Node.js with Polka, WebSockets, and OpenTelemetry
Maintainers
Readme
@sveltejs/adapter-node-rolldown
High-performance SvelteKit adapter for Node.js with Polka, WebSocket support, OpenTelemetry tracing, and optimizations for ECS/Kubernetes deployments.
Features
- 🚀 Ultra-fast: Uses Polka (~30x faster than Express)
- � Bundled: Uses Rollup to bundle server code (only production deps needed)
- 🔧 Instrumentation: Full support for SvelteKit's instrumentation API
- �🗜️ Smart compression: Gzip/Brotli with pre-compression support
- 🔌 WebSocket support: Built-in WebSocket server with
ws - 📊 OpenTelemetry: Full distributed tracing with Dynatrace integration
- 🏥 Health checks:
/healthand/readinessendpoints for K8s - 🔄 Graceful shutdown: Proper SIGTERM handling for zero-downtime deployments
- 📦 High body limits: Configurable request size limits (default 10MB)
- 🎯 Container-ready: Optimized for Docker, ECS, and Kubernetes
Installation
npm install @sveltejs/adapter-node-rolldownUsage
In your svelte.config.js:
import adapter from '@sveltejs/adapter-node-rolldown';
export default {
kit: {
adapter: adapter({
// All options are optional with sensible defaults
out: 'build',
precompress: true,
compression: true,
compressionLevel: 6,
bodyLimit: '10mb',
websocket: true,
websocketPath: '/ws',
telemetry: true,
telemetrySampleRate: 1.0,
healthCheck: true,
gracefulShutdownTimeout: 30000,
// 🆕 Rolldown bundling options
external: ['polka', 'sirv', 'compression', 'ws'], // Keep these external
// OR use a function:
// external: (pkg) => Object.keys(pkg.dependencies || {}),
bundleAll: false, // true = bundle everything (no node_modules at runtime)
rolldownOptions: {} // Additional rolldown config
})
}
};Configuration Options
Basic Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| out | string | 'build' | Output directory for the build |
| precompress | boolean | true | Pre-compress static assets with gzip and brotli |
| envPrefix | string | '' | Prefix for environment variables |
Performance Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| compression | boolean | true | Enable runtime compression middleware |
| compressionLevel | number | 6 | Compression level (1-9) |
| bodyLimit | string\|number | '10mb' | Maximum request body size |
WebSocket Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| websocket | boolean | true | Enable WebSocket support |
| websocketPath | string | '/ws' | WebSocket endpoint path |
OpenTelemetry Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| telemetry | boolean | true | Enable OpenTelemetry tracing |
| telemetrySampleRate | number | 1.0 | Sampling rate (0.0-1.0) |
| telemetryConfig | object | {} | Additional telemetry configuration |
Container Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| healthCheck | boolean | true | Enable health check endpoints |
| gracefulShutdownTimeout | number | 30000 | Graceful shutdown timeout (ms) |
| polyfill | boolean | true | Inject global polyfills |
Bundling Options 🆕
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| external | string[] or function | undefined | Packages to exclude from bundle (see examples below) |
| bundleAll | boolean | false | Bundle everything including dependencies |
| rolldownOptions | object | {} | Additional Rolldown configuration |
External Package Examples
Array of packages:
adapter({
external: ['polka', 'sirv', 'compression', 'ws']
})Function with custom logic:
adapter({
external: (pkg) => {
// Keep runtime packages external
const runtime = ['polka', 'sirv', 'compression', 'ws'];
// Bundle OpenTelemetry to avoid version conflicts
return runtime;
}
})Bundle everything (no externals):
adapter({
bundleAll: true // Zero node_modules at runtime
})Default (uses package.json dependencies):
adapter({
// No external option = uses package.json dependencies
})Environment Variables
Server Configuration
# Server binding
PORT=3000
HOST=0.0.0.0
# Performance tuning
KEEP_ALIVE_TIMEOUT=65000
HEADERS_TIMEOUT=66000
MAX_REQUESTS_PER_SOCKET=0
# Trust proxy headers (if behind load balancer)
TRUST_PROXY=trueOpenTelemetry Configuration
# Enable/disable telemetry at runtime
OTEL_ENABLED=true
# Service identification
OTEL_SERVICE_NAME=my-sveltekit-app
OTEL_SERVICE_VERSION=1.0.0
# Exporter configuration
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-env.live.dynatrace.com/api/v2/otlp/v1/traces
OTEL_EXPORTER_OTLP_PROTOCOL=http # or 'grpc'
# Dynatrace API token (for authentication)
DYNATRACE_API_TOKEN=your-api-token-here
# Sampling rate (override adapter config)
OTEL_SAMPLE_RATE=1.0WebSocket Configuration
# Enable/disable at runtime
WEBSOCKET_ENABLED=true
WEBSOCKET_PATH=/wsDeployment
Building
npm run build
cd build
npm install --omit=dev
node index.jsDocker
Example Dockerfile:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
# Install only production dependencies
COPY --from=builder /app/build/package.json ./
RUN npm install --omit=dev
# Copy built application
COPY --from=builder /app/build ./
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
EXPOSE 3000
CMD ["node", "index.js"]Build and run:
docker build -t my-sveltekit-app .
docker run -p 3000:3000 \
-e OTEL_ENABLED=true \
-e OTEL_EXPORTER_OTLP_ENDPOINT=https://your-env.live.dynatrace.com/api/v2/otlp/v1/traces \
-e DYNATRACE_API_TOKEN=your-token \
my-sveltekit-appKubernetes
Example deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sveltekit-app
spec:
replicas: 3
selector:
matchLabels:
app: sveltekit-app
template:
metadata:
labels:
app: sveltekit-app
spec:
containers:
- name: app
image: my-sveltekit-app:latest
ports:
- containerPort: 3000
name: http
protocol: TCP
env:
- name: PORT
value: "3000"
- name: HOST
value: "0.0.0.0"
- name: OTEL_ENABLED
value: "true"
- name: OTEL_SERVICE_NAME
value: "sveltekit-app"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
valueFrom:
secretKeyRef:
name: dynatrace-config
key: endpoint
- name: DYNATRACE_API_TOKEN
valueFrom:
secretKeyRef:
name: dynatrace-config
key: api-token
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /readiness
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
---
apiVersion: v1
kind: Service
metadata:
name: sveltekit-app
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 3000
protocol: TCP
name: http
selector:
app: sveltekit-appECS Task Definition
{
"family": "sveltekit-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"containerDefinitions": [
{
"name": "app",
"image": "my-sveltekit-app:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{ "name": "PORT", "value": "3000" },
{ "name": "HOST", "value": "0.0.0.0" },
{ "name": "OTEL_ENABLED", "value": "true" },
{ "name": "OTEL_SERVICE_NAME", "value": "sveltekit-app" }
],
"secrets": [
{
"name": "DYNATRACE_API_TOKEN",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:dynatrace-token"
},
{
"name": "OTEL_EXPORTER_OTLP_ENDPOINT",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:dynatrace-endpoint"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "wget -q --spider http://localhost:3000/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 40
},
"stopTimeout": 35,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/sveltekit-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}WebSocket Usage
The adapter includes a WebSocket server on /ws by default. You can customize the WebSocket behavior by modifying files/index.js:
wsServer.on('connection', (ws, req) => {
console.log('WebSocket connection established');
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
// Your custom WebSocket logic here
ws.send(JSON.stringify({
type: 'response',
data: 'processed'
}));
});
});Client-side usage:
const ws = new WebSocket('ws://localhost:3000/ws');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'hello' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};Server Instrumentation
The adapter fully supports SvelteKit's instrumentation API. Create a src/instrumentation.server.js file in your SvelteKit project:
// src/instrumentation.server.js
export async function initialize() {
console.log('Server instrumentation initialized!');
// Initialize monitoring, databases, caches, etc.
await connectToDatabase();
await initializeCache();
// This runs before the server starts accepting requests
}The instrumentation file will be automatically bundled and executed before your server starts.
Instrumentation with OpenTelemetry
You can combine instrumentation with OpenTelemetry for enhanced monitoring:
// src/instrumentation.server.js
import { trace } from '@opentelemetry/api';
export async function initialize() {
const tracer = trace.getTracer('app-initialization');
const span = tracer.startSpan('server-init');
try {
await setupApplication();
span.addEvent('Application setup complete');
} catch (error) {
span.recordException(error);
throw error;
} finally {
span.end();
}
}OpenTelemetry & Dynatrace
The adapter automatically instruments:
- HTTP requests (incoming and outgoing)
- Database queries (PostgreSQL, MySQL, MongoDB, Redis)
- External API calls
- Custom spans (if needed)
Dynatrace Setup
- Get your Dynatrace environment ID and API token
- Set environment variables:
OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=https://{your-env-id}.live.dynatrace.com/api/v2/otlp/v1/traces
DYNATRACE_API_TOKEN=your-api-token
OTEL_SERVICE_NAME=my-sveltekit-app- Traces will appear in Dynatrace's Distributed Traces view
Custom Spans
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-app');
export async function load() {
const span = tracer.startSpan('custom-operation');
try {
// Your code here
span.addEvent('Processing started');
const result = await processData();
span.setAttribute('result.count', result.length);
return { data: result };
} finally {
span.end();
}
}Performance Benchmarks
Compared to @sveltejs/adapter-node:
- ~30x faster routing (Polka vs Express)
- ~40% smaller bundle size
- ~25% faster cold starts
- Pre-compression: Serve
.gzand.brfiles directly (zero CPU cost) - High throughput: Handle 10MB+ request bodies without issues
Monitoring & Observability
Health Check Endpoints
GET /health- Liveness probe (returns 200 if server is alive)GET /readiness- Readiness probe (returns 200 if ready to accept traffic)
Metrics (via OpenTelemetry)
The adapter automatically collects:
- Request duration and count
- HTTP status codes
- Database query performance
- Memory and CPU usage
- WebSocket connections
Graceful Shutdown
The server handles SIGTERM and SIGINT signals:
- Stop accepting new connections
- Close existing WebSocket connections
- Flush OpenTelemetry traces
- Exit after timeout (default 30s)
Troubleshooting
High Memory Usage
Reduce OpenTelemetry sampling rate:
adapter({
telemetrySampleRate: 0.1 // Sample only 10% of requests
})WebSocket Connection Issues
Ensure your load balancer supports WebSocket upgrades:
- ALB: Enable sticky sessions
- NGINX: Add
proxy_http_version 1.1;andproxy_set_header Upgrade $http_upgrade;
Compression Not Working
Check content types and sizes:
- Minimum size: 1KB (threshold)
- Images/videos are not compressed
- Pre-compressed files (.gz, .br) are served if available
License
MIT
Contributing
Contributions welcome! Please open an issue or PR on GitHub.
