2025-04-15 Web Development, Programming, Productivity
Markdown-to-PDF API with a Vercel Frontend and a DigitalOcean Backend
By O. Wolfson
I recently built a secure, full-stack Markdown-to-PDF converter using:
- A Vercel-hosted frontend (Next.js 15, App Router, Server & Client Components)
- A Node.js backend (Express + Puppeteer) hosted on a DigitalOcean droplet
- Caddy to handle HTTPS with Letโs Encrypt
- A custom subdomain:
md2pdfapi.owolf.com
View the live demo at md2pdf.owolf.com/
Hereโs a step-by-step breakdown of what I did.
๐ ๏ธ Step 1: Set Up the Backend on a DigitalOcean Droplet
๐น 1. Create a Droplet
- Chose a basic Ubuntu droplet (1 vCPU / 1 GB RAM)
- Connected via DigitalOcean's web console
๐น 2. Installed Required Software
๐น 3. Created My Project Folder
๐ Step 2: Created the API with Puppeteer + Express
I wrote a Node script (server.js
) that:
- Parses markdown using
markdown-it
- Uses Puppeteer to render HTML and export it as a PDF
- Exposes a POST
/api/convert
route that accepts markdown and returns a PDF
๐ Step 3: Ran It with PM2
Now my backend runs continuously and restarts on reboot.
๐ Step 3.5: Point a Subdomain to the Droplet (A Record in Vercel)
Since my domain owolf.com
is managed through Vercel DNS, I created a subdomain to point to my API server.
๐น 1. Opened the Vercel dashboard โ Domains โ owolf.com
๐น 2. Added a new A record:
Type | Name | Value |
---|---|---|
A | md2pdfapi | 157.230.39.117 |
This created:
md2pdfapi.owolf.com โ 157.230.39.117
Which is the public IP of my DigitalOcean droplet.
This DNS entry is required for Caddy to issue an HTTPS certificate for the subdomain using Letโs Encrypt.
๐ Step 4: Added a Domain and HTTPS with Caddy
๐น 1. Pointed a subdomain (md2pdfapi.owolf.com
) to the droplet IP using an A record.
๐น 2. Installed and configured Caddy:
๐น 3. Caddyfile:
Caddy issued a Letโs Encrypt cert and started serving HTTPS.
๐ฅ๏ธ Step 5: Built the Frontend on Vercel with Next.js 15
๐น 1. Created a new app with App Router and Server Components
๐น 2. Installed ShadCN for UI components
๐น 3. Added a client component:
๐ Step 6: Added Security Measures
๐ก๏ธ Rate Limiting the API
To prevent abuse and protect server resources, I added basic rate limiting to the API using the express-rate-limit
middleware.
This setup limits clients to 10 requests per minute per IP:
If a user exceeds the limit, they receive a 429 Too Many Requests
response.
This keeps the service stable even under heavy traffic or by mistake-triggered loops.
โ Final Result
- The Vercel frontend sends markdown to
https://md2pdfapi.owolf.com/api/convert
- The droplet returns a freshly rendered PDF
- Fully HTTPS-secured
- Fully serverless on the frontend, persistent on the backend
๐ Takeaways
This was a great project for practicing:
- Multi-host architecture (Vercel + DigitalOcean)
- Caddy for secure reverse proxying
- Clean API separation
- Server-side rendering with Puppeteer
Youโre welcome to reuse this setup for any static-to-PDF workflow, including:
- Invoices
- Blog post exports
- Reports or templates
- Serverless tools with an on-demand rendering backend