I wanted a private, client-side PDF manipulation toolkit on my own domain (pdf.meinard.dev) without running a dedicated VPS. I forked BentoPDF and deployed it to Netlify's free tier.
Because BentoPDF executes everything client-side using WebAssembly (including a compiled LibreOffice binary for Office-to-PDF conversions), standard static hosting requires specific HTTP headers to work.
1. Fork & Connect to Netlify
- Fork
alam00000/bentopdfto your GitHub account. - Import the repository into Netlify:
- Build command:
npm run build - Publish directory:
dist - Node version:
18or20
- Build command:
2. Header Configuration (netlify.toml)
LibreOffice WASM requires SharedArrayBuffer, which modern browsers only enable when the page is cross-origin isolated. Additionally, pre-compressed .wasm.gz assets must be served with Content-Encoding: gzip.
Add this netlify.toml to your project root:
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
# Enable SharedArrayBuffer for LibreOffice WASM
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Resource-Policy = "cross-origin"
# Serve pre-compressed WASM binaries with proper headers
[[headers]]
for = "/libreoffice-wasm/soffice.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/libreoffice-wasm/soffice.data.gz"
[headers.values]
Content-Type = "application/octet-stream"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "*.wasm"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Content-Type = "application/wasm"
# SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 2003. Troubleshooting
- Conversion stuck at 55%: Verify
window.crossOriginIsolated === truein the browser console. Iffalse, theCOEP/COOPheaders are missing. - Magic word error (
00 61 73 6dexpected): The.wasm.gzfiles were served withoutContent-Encoding: gzip, causing raw gzip bytes to be fed into the WASM compiler.
4. Custom Domain
Under Site settings → Domain management, point a CNAME for pdf.meinard.dev to Netlify's DNS target.