A Complete Guide to Deploying Express.js on Cloudflare Workers and Vercel
Deploying a Node.js/Express.js application on serverless platforms like Cloudflare Workers and Vercel can dramatically simplify infrastructure management and improve global performance. However, each environment has its own constraints and pitfalls. In this guide, we’ll translate and adapt proven best practices—originally documented in Chinese—into clear, SEO-optimized English content. You’ll learn:
-
How to prepare and configure your Express.js code -
How to deploy seamlessly on Cloudflare Workers using Wrangler -
How to deploy on Vercel with zero configuration -
How to troubleshoot the most common runtime errors -
FAQs and JSON-LD schema for enhanced Google visibility
Let’s dive in.
Table of Contents
Introduction
The rise of serverless platforms offers developers a frictionless path to global application delivery. Cloudflare Workers positions your code at the edge of the network, while Vercel provides seamless Node.js function deployments. Both eliminate server maintenance, but each requires slight code and configuration changes.
In this guide, you will:
-
Gain hands-on steps to deploy Express.js on each platform -
Understand environment constraints (e.g., file system, supported modules) -
Learn how to resolve errors you will inevitably encounter -
Follow SEO-aligned structuring—clear headings, schema markup, and strategic keyword placement—to maximize search visibility
By the end, your Express.js app will be running worldwide, and your blog post will stand out in Google search results.
Why Serverless—Cloudflare Workers vs. Vercel
Feature | Cloudflare Workers | Vercel (Node.js Serverless) |
---|---|---|
Runtime | Runs on V8 isolates using fetch API |
Node.js functions in AWS Lambda |
File System | Read-only (only /tmp writable) |
Read-only (only /tmp writable) |
Supported Node.js Modules | Limited; enable node_compat for some |
Full core module support |
Cold Start Time | Sub-millisecond at edge | Tens to hundreds of ms |
Global Distribution | Instant edge propagation | CDN caching per region |
Zero-Config Deployment | Requires Wrangler config | Auto-detects Node.js, minimal setup |
Serverless means no servers to provision or manage. Cloudflare Workers excels in edge performance, while Vercel shines in developer experience. Choose based on your app’s needs.
Part 1: Deploying on Cloudflare Workers
Quick Question: Why can’t we run Express.js out-of-the-box on Workers?
Answer: Workers use the standardfetch
event and do not load full Node.js core modules likehttp
orfs
. You must adapt routing and middleware accordingly or use a compatibility framework.
1.1 Install Wrangler CLI
Wrangler is the official CLI for building and publishing Workers. Install it globally:
npm install -g wrangler
Tip: Always keep Wrangler up-to-date to benefit from the latest features and bug fixes.
1.2 Initialize a Worker Project
Create and scaffold a new Worker:
npx wrangler init my-worker
cd my-worker
-
my-worker
can be any project name -
You’ll see wrangler.toml
and a sampleindex.js
1.3 Adapt Your Express.js App
Express middleware and routing must be rewritten:
-
Route Handling: Convert app.get()
andapp.post()
tofetch
event logic. -
Replace Unsupported Modules: For file operations, use Cloudflare KV or Durable Objects instead of fs
. -
Use Worker-Friendly Frameworks: Frameworks like Hono offer Express-like syntax on Workers.
Example Adaptation:
// Express style app.get('/api/data', (req, res) => { … }); // Worker style addEventListener('fetch', event => { const url = new URL(event.request.url); if (url.pathname === '/api/data') { event.respondWith(new Response(JSON.stringify(data), { headers: {'Content-Type': 'application/json'} })); } });
1.4 Configure wrangler.toml
Open wrangler.toml
and set:
name = "my-worker"
type = "javascript"
[build]
command = "npm run build" # If you have a build step
upload_format = "service-worker"
node_compat = true # Allows some Node.js APIs
[env.production]
route = "https://yourdomain.com/*"
zone_id = "your-zone-id"
-
node_compat = true
lets you import certain Node modules. -
Use environment-specific sections to separate dev and prod.
1.5 Write the Worker Entry Script
Replace the sample index.js
with:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello from Cloudflare Worker!", {
headers: { "Content-Type": "text/plain" }
});
}
// Additional routes…
return new Response("Not Found", { status: 404 });
}
};
-
Handle all routing within a single fetch
handler. -
Use JSON responses for APIs: new Response(JSON.stringify(data))
.
1.6 Local Development and Testing
Run a local server to simulate Worker behavior:
wrangler dev
# Opens a preview at http://localhost:8787
-
Test all endpoints and static assets. -
Use browser dev tools or curl
to validate responses.
1.7 Publish to Cloudflare
Once tested, deploy to the edge:
wrangler publish --env production
-
Workers propagate globally within seconds. -
Verify on your custom domain or the Workers.dev subdomain.
Part 2: Deploying on Vercel
Quick Question: Why is Express.js deployment easier on Vercel?
Answer: Vercel auto-detects Node.js apps. Exporting an Express app asexport default app
turns each route into a serverless function.
2.1 Prepare Your Express App for Export
In index.js
:
import express from 'express';
const app = express();
// Sample route
app.get('/', (req, res) => {
res.send('Hello from Express on Vercel!');
});
// Local dev only
if (process.env.NODE_ENV !== 'production' && !process.env.VERCEL) {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
}
export default app;
-
Key: export default app
for Vercel to pick up.
2.2 (Optional) Create vercel.json
Customize build and routing:
{
"version": 2,
"builds": [
{ "src": "index.js", "use": "@vercel/node" }
],
"routes": [
{ "src": "/(.*)", "dest": "/index.js" }
]
}
-
You can omit vercel.json
—Vercel infers defaults.
2.3 Connect Repository and Auto-Deploy
-
Push to GitHub/GitLab/Bitbucket. -
On Vercel Dashboard, click New Project > Import your repo. -
Confirm the detected Framework: Node.js. -
Set environment variables if needed. -
Click Deploy.
Every push to the main branch triggers a fresh deployment.
2.4 Using the Vercel CLI
For local deployments and previews:
npm install -g vercel
vercel # Link project
vercel --prod # Deploy to production
-
Use vercel --previews
for staging deployments.
Part 3: Common Deployment & Runtime Errors
Even with clear steps, you may hit errors. Below are the top five, their causes, and fixes.
3.1 ERR_MODULE_NOT_FOUND (Case Sensitivity)
Symptom:
Error: Cannot find module '/var/task/themes/markdown/Notion.js'
Cause:
Linux file systems (used by Vercel) are case-sensitive. Local Windows/macOS may not be.
Solution:
Ensure import statements match file names exactly:
- import notion from './markdown/Notion.js';
+ import notion from './markdown/notion.js';
3.2 EROFS: Read-Only File System
Symptom:
Error: EROFS: read-only file system, mkdir '/var/task/uploads'
Cause:
Serverless environments do not allow write access outside /tmp
.
Solution:
Configure file uploads (e.g., Multer) to use /tmp
:
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, '/tmp/uploads');
},
filename(req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
3.3 ReferenceError: path
Is Not Defined
Symptom:
ReferenceError: path is not defined
Cause:
Forgot to import Node’s path
module.
Solution:
Add at top of file:
import path from 'path';
3.4 ENOENT: No Such File or Directory
Symptom:
Error: ENOENT: no such file or directory, open '/tmp/uploads/abc.png'
Cause:
Upload directory didn’t exist or relative path used.
Solution:
Ensure directory creation and absolute path:
fs.mkdirSync('/tmp/uploads', { recursive: true });
Also verify req.file.path
starts with /tmp/
.
3.5 Binary Response Artifacts
Symptom:
Client sees b'\x0c\x90…'
instead of JSON or HTML.
Cause:
Response serialization mismatch; raw buffer sent without correct headers.
Solution:
Always use Express helpers:
res.json({ html: htmlContent });
// or
res.send(htmlContent);
And set headers explicitly if needed:
res.set('Content-Type', 'text/html');
Add console logs before returning to inspect types:
console.log(typeof htmlContent, htmlContent.length);
Part 4: SEO Best Practices for Serverless Tech Blogs
-
Use Clear, Keyword-Rich Headings
-
H1: “Guide to Deploying Express.js on Cloudflare Workers” -
H2–H3: Break down by platform and error types.
-
-
Write Concise Meta Descriptions
-
Under 160 characters summarizing benefits and steps.
-
-
Include JSON-LD Schema
-
FAQPage and HowTo types help Google display rich snippets.
-
-
Optimize for Readability
-
Short paragraphs, bulleted lists, and tables.
-
-
Internal & External Linking
-
Link to official docs for Wrangler, Vercel, Express.
-
-
Use ALT Text for Images/Diagrams
-
E.g., 
-
-
Provide Code Snippets with Language Tags
-
js …
-
-
Add a Table of Contents with Anchor Links
-
Improves UX and dwell time.
-
-
Answer Common Questions (FAQ Section)
-
Address beginner and edge-case scenarios.
-
FAQ
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is the difference between Cloudflare Workers and traditional Node.js?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Cloudflare Workers run on V8 isolates using the fetch API and offer a read-only file system, whereas traditional Node.js supports full core modules and persistent file access."
}
},
{
"@type": "Question",
"name": "Why must I use /tmp for file uploads in serverless?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Serverless functions have a read-only root file system; the only writable directory is /tmp for temporary storage."
}
},
{
"@type": "Question",
"name": "How do I avoid case-sensitivity import errors on Vercel?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Ensure that your import statements match the exact file name casing on disk, since Linux environments are case-sensitive."
}
}
]
}
</script>
Click to expand common troubleshooting questions
-
How do I install Wrangler CLI?
Runnpm install -g wrangler
. -
How to trigger Vercel auto-deploy?
Link your Git repo; every push to the main branch auto-deploys. -
Multer upload errors—what to check?
Verify directory existence and absolute paths under/tmp
.
HowTo & FAQ Schema (JSON-LD)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "Deploy Express.js on Vercel",
"description": "Step-by-step instructions to prepare an Express.js app and deploy it on Vercel.",
"step": [
{
"@type": "HowToStep",
"name": "Export the Express app",
"text": "In index.js, import express and export default app."
},
{
"@type": "HowToStep",
"name": "Configure vercel.json",
"text": "Add the builds and routes sections if customizing."
},
{
"@type": "HowToStep",
"name": "Connect Git and deploy",
"text": "Push to GitHub and import the project in the Vercel dashboard."
}
]
}
</script>