repo-root/
├── core/ ← shared source (never deployed directly)
│ ├── fauxBold.js
│ ├── sharedRender.js
│ ├── renderPool.js
│ ├── renderWorker.js
│ └── NotoSans-Subset.ttf
├── scripts/
│ └── build.mjs ← copies core/ → {platform}/lib/
├── cloudflare/ ← wrangler deploy root
│ ├── worker.js
│ ├── wrangler.jsonc
│ └── package.json
├── netlify/ ← Netlify base directory
│ ├── netlify.toml
│ ├── package.json
│ ├── functions/
│ │ └── rasterize.js
│ └── lib/ ← GENERATED (gitignored)
├── vercel/ ← Vercel root directory
│ ├── vercel.json
│ ├── package.json
│ ├── api/
│ │ └── rasterize.js
│ └── lib/ ← GENERATED (gitignored)
├── render/ ← Render.com rootDir
│ ├── render.yaml
│ ├── Dockerfile
│ ├── package.json
│ ├── server.js
│ ├── discord.js
│ └── lib/ ← GENERATED (gitignored)
├── vps/ ← Generic Node / Pterodactyl / Fly / Railway
│ ├── Dockerfile
│ ├── package.json
│ ├── server.js
│ ├── discord.js
│ └── lib/ ← GENERATED (gitignored)
├── docker-compose.yml
└── .gitignore
core/font.js ← replaced by core/sharedRender.js
core/logic.js ← replaced by direct applyFauxBold calls
scripts/embed-font.mjs ← replaced by scripts/build.mjs
netlify-node/ ← entire old folder
node/ ← entire old folder (renamed to vps/)
render-node/ ← entire old folder (renamed to render/)
vercel-node/ ← entire old folder (renamed to vercel/)
edge-node/ ← entire old folder (renamed to cloudflare/)
# Generated by scripts/build.mjs
netlify/lib/
vercel/lib/
render/lib/
vps/lib/
**/node_modules/
cloudflare/.wrangler/cd cloudflare
npm install
npx wrangler deploy- Root directory:
cloudflare/ - No build step — wrangler bundles
../core/imports automatically - Font loaded as a Data binding via
wrangler.jsoncrules (.ttfglob)
UI settings:
| Setting | Value |
|---|---|
| Base directory | netlify |
| Build command | (leave blank — read from netlify.toml) |
| Publish directory | (leave blank) |
| Functions directory | netlify/functions |
The netlify.toml handles everything:
command = "node ../scripts/build.mjs netlify"This runs build.mjs from netlify/, which goes one level up (../) to reach scripts/ and core/, then copies them into netlify/lib/.
UI settings:
| Setting | Value |
|---|---|
| Root directory | vercel |
| Build command | (leave blank — read from vercel.json) |
| Output directory | (leave blank) |
vercel.json handles the build:
"buildCommand": "node ../scripts/build.mjs vercel"UI settings:
| Setting | Value |
|---|---|
| Root directory | render |
| Build command | (read from render.yaml) |
| Start command | npm start |
render.yaml:
buildCommand: "npm install && node ../scripts/build.mjs render"
startCommand: npm startEnvironment variables:
MAX_CONCURRENT— number of worker threads (default: 2, set based on instance CPU)DISCORD_WEBHOOK_URL— webhook URL for dashboard (required)PORT— set automatically by Render
Docker (recommended):
# Build context must be repo root
docker build -f vps/Dockerfile -t rasterize-vps .
docker run -p 3000:3000 \
-e DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..." \
-e MAX_CONCURRENT=4 \
rasterize-vpsManual:
cd vps
npm install
node ../scripts/build.mjs vps # or: npm run build
npm startEnvironment variables:
MAX_CONCURRENT— worker threads (default: 4)DISCORD_WEBHOOK_URL— webhook URLNODE_NAME— display name in Discord dashboardPORT— HTTP port (default: 3000)
# From repo root
docker compose up --build
curl http://localhost:3001/health # render-node
curl http://localhost:3002/health # vps-nodescripts/build.mjs
│
├── reads: core/fauxBold.js
├── reads: core/sharedRender.js
├── reads: core/renderPool.js
├── reads: core/renderWorker.js
└── reads: core/NotoSans-Subset.ttf
│
└── writes to: {platform}/lib/
renderWorker.js uses createRequire(workerData.serverDir) to resolve @resvg/resvg-js from the platform's own node_modules/, regardless of where the worker file physically lives.
Every node in the fleet:
- Loads only
NotoSans-Subset.ttf(Regular) —loadSystemFonts: false, nofontDirs - Calls
applyFauxBold()before every render — addsstroke-width: 0.035emto bold elements - Covers
font-weight="bold",style="font-weight: bold",font-weight="600–900", and<tspan>children
This makes all nodes produce pixel-consistent output regardless of what system fonts the host OS has installed.