Deploying a Node.js Image Generation Service on Railway
A technical tutorial explaining how to configure and host an image manipulation microservice without performance issues.
Deploying a Node.js Image Generation Service on Railway
Generating dynamic images (like OpenGraph banners, personalized invoices, or custom certificates) on the fly is a classic requirement for modern web apps. However, if you've ever tried to deploy an image manipulation library like Puppeteer or Canvas to a standard serverless function or container on Railway, you've likely encountered strict memory limits, missing shared OS libraries, and dreaded timeout errors.
In this post, we will look at the harsh reality of hosting your own Chromium instances, and why offloading this workload to a specialized API like OGCraft is the smartest architectural decision for your Node.js or Express backend.
The Pain of DIY: Puppeteer on Railway
A standard Express.js server acts as the entry point. However, simply launching a new Chromium instance for every incoming HTTP request will quickly consume all available RAM and crash your Railway container.
import express from 'express';
import puppeteer from 'puppeteer';
const app = express();
app.get('/api/generate', async (req, res) => {
// WARNING: Launching a browser per request will crash your Railway container!
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-dev-shm-usage']
});
const page = await browser.newPage();
await page.setContent('<h1>Hello World</h1>');
const buffer = await page.screenshot();
await browser.close();
res.setHeader('Content-Type', 'image/png');
res.send(buffer);
});Beyond the code, you also have to manage a massive custom Dockerfile just to install missing OS-level fonts (like CJK fonts to prevent the dreaded "tofu" squares) and X11 libraries required by Chromium. This adds hundreds of megabytes to your Docker image.
The OGCraft Solution
Instead of fighting with Docker, Nixpacks, and out-of-memory (OOM) kills, you can use OGCraft. You simply pass your parameters via URL, and OGCraft handles the rendering in milliseconds.
In your Node.js API, you just construct the URL and either redirect the client or return the image URL:
import express from 'express';
const app = express();
app.get('/api/social-image', (req, res) => {
const title = req.query.title || 'Default Title';
// Construct parameters based on the request
const params = new URLSearchParams({
template: 'modern-blog',
title: title.toString(),
theme: 'dark',
});
// Generate the OGCraft URL
const ogImageUrl = `https://api.ogcraft.dev/api/v1/generate?${params.toString()}`;
// Option A: 302 Redirect the bot straight to OGCraft (Saves bandwidth!)
return res.redirect(302, ogImageUrl);
// Option B: Return JSON if calling from a frontend client
// res.json({ url: ogImageUrl });
});
app.listen(3000);Notice the res.redirect(302). By instructing social scrapers (like TwitterBot) to follow the redirect to OGCraft's Edge CDN, your Railway service never actually downloads or streams the heavy PNG payload, saving you massive amounts of CPU and egress bandwidth.
Conclusion
Dynamic OpenGraph images are no longer an optional luxury—they are a critical component of modern web SEO and social media marketing. Regardless of your stack, injecting custom social cards into your web pages is essential for standing out.
Ready to take your social share preview images to the next level without maintaining complex canvas code?
Build Stunning OG Cards with OGCraft
Customize templates in real-time in our interactive Playground and fetch high-performance OpenGraph images via a simple API URL.