This guide covers how to deploy Reader in production environments.
Server Usage Pattern
For server applications, create one ReaderClient instance and reuse it:
import { ReaderClient } from "@vakra-dev/reader" ;
import express from "express" ;
// Create client once at startup
const reader = new ReaderClient ({
browserPool: {
size: 5 ,
retireAfterPages: 50 ,
retireAfterMinutes: 15 ,
},
});
const app = express ();
app . use ( express . json ());
// Reuse client for all requests
app . post ( "/scrape" , async ( req , res ) => {
try {
const result = await reader . scrape ({
urls: req . body . urls ,
formats: [ "markdown" ],
});
res . json ( result );
} catch ( error ) {
res . status ( 500 ). json ({ error: error . message });
}
});
// Graceful shutdown
process . on ( "SIGTERM" , async () => {
console . log ( "Shutting down..." );
await reader . close ();
process . exit ( 0 );
});
app . listen ( 3000 , () => {
console . log ( "Server running on port 3000" );
});
Docker Deployment
Docker deployment only works on x86_64 Linux . Apple Silicon Macs require native Node.js or a remote x86_64 server.
Dockerfile
FROM node:20-slim
# Install Chrome dependencies
RUN apt-get update && apt-get install -y \
chromium \
fonts-liberation \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
xdg-utils \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV NODE_ENV=production
ENV CHROME_PATH=/usr/bin/chromium
EXPOSE 3000
CMD [ "node" , "server.js" ]
docker-compose.yml
version : "3.8"
services :
reader :
build : .
ports :
- "3000:3000"
environment :
- NODE_ENV=production
deploy :
resources :
limits :
memory : 4G
reservations :
memory : 2G
restart : unless-stopped
Production Best Practices
Browser Pool Configuration
const reader = new ReaderClient ({
browserPool: {
size: 5 , // Balance concurrency vs memory
retireAfterPages: 50 , // Prevent memory leaks
retireAfterMinutes: 15 ,
maxQueueSize: 100 , // Prevent unbounded queue growth
},
});
Error Handling
import {
ReaderClient ,
TimeoutError ,
NetworkError ,
CloudflareError
} from "@vakra-dev/reader" ;
app . post ( "/scrape" , async ( req , res ) => {
try {
const result = await reader . scrape ({
urls: req . body . urls ,
timeoutMs: 30000 ,
});
res . json ( result );
} catch ( error ) {
if ( error instanceof TimeoutError ) {
res . status ( 504 ). json ({ error: "Request timed out" });
} else if ( error instanceof NetworkError ) {
res . status ( 502 ). json ({ error: "Network error" });
} else if ( error instanceof CloudflareError ) {
res . status ( 403 ). json ({ error: "Access denied" });
} else {
res . status ( 500 ). json ({ error: "Internal error" });
}
}
});
Rate Limiting
import rateLimit from "express-rate-limit" ;
const limiter = rateLimit ({
windowMs: 60 * 1000 , // 1 minute
max: 100 , // 100 requests per minute
});
app . use ( "/scrape" , limiter );
Health Check
app . get ( "/health" , ( req , res ) => {
const ready = reader . isReady ();
res . status ( ready ? 200 : 503 ). json ({
status: ready ? "healthy" : "unhealthy" ,
ready ,
});
});
Graceful Shutdown
let isShuttingDown = false ;
const shutdown = async () => {
if ( isShuttingDown ) return ;
isShuttingDown = true ;
console . log ( "Shutting down gracefully..." );
// Stop accepting new requests
server . close ();
// Close reader
await reader . close ();
process . exit ( 0 );
};
process . on ( "SIGTERM" , shutdown );
process . on ( "SIGINT" , shutdown );
Memory Considerations
Each browser instance uses ~200-500MB of memory. Plan accordingly:
Pool Size Recommended RAM 1-2 2GB 3-5 4GB 5-10 8GB 10+ 16GB+
Process Exit Note
Node.js may not auto-exit after reader.close() due to Hero internals. For scripts, add process.exit(0) after close. For servers, this is expected behavior since the process stays alive to handle requests.
Next Steps
Browser Pool Configure browser pool
API Reference Full API documentation