feat: add custom branding, air-gapped deployment script, and updated self-hosting docs
This commit is contained in:
@@ -17,6 +17,12 @@ npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
To customize branding, set environment variables before building:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Step 2: Copy Files
|
||||
|
||||
```bash
|
||||
@@ -52,6 +58,9 @@ Create `/etc/apache2/sites-available/bentopdf.conf`:
|
||||
# WASM MIME type
|
||||
AddType application/wasm .wasm
|
||||
|
||||
# Prevent double-compression of pre-compressed files
|
||||
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
|
||||
|
||||
# Compression
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
|
||||
@@ -67,9 +76,24 @@ Create `/etc/apache2/sites-available/bentopdf.conf`:
|
||||
ExpiresByType image/svg+xml "access plus 1 year"
|
||||
</IfModule>
|
||||
|
||||
# Required headers for SharedArrayBuffer (LibreOffice WASM)
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
Header always set Cross-Origin-Resource-Policy "cross-origin"
|
||||
|
||||
# Security headers
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
# Pre-compressed LibreOffice WASM files
|
||||
<FilesMatch "soffice\.wasm\.gz$">
|
||||
ForceType application/wasm
|
||||
Header set Content-Encoding "gzip"
|
||||
</FilesMatch>
|
||||
<FilesMatch "soffice\.data\.gz$">
|
||||
ForceType application/octet-stream
|
||||
Header set Content-Encoding "gzip"
|
||||
</FilesMatch>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
@@ -191,6 +215,30 @@ Check that mod_rewrite is enabled:
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
### Word/ODT/Excel to PDF Not Working
|
||||
|
||||
LibreOffice WASM requires `SharedArrayBuffer`, which needs these headers:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
```
|
||||
|
||||
The pre-compressed `.wasm.gz` and `.data.gz` files also need correct `Content-Encoding`:
|
||||
|
||||
```apache
|
||||
<FilesMatch "soffice\.wasm\.gz$">
|
||||
ForceType application/wasm
|
||||
Header set Content-Encoding "gzip"
|
||||
</FilesMatch>
|
||||
<FilesMatch "soffice\.data\.gz$">
|
||||
ForceType application/octet-stream
|
||||
Header set Content-Encoding "gzip"
|
||||
</FilesMatch>
|
||||
```
|
||||
|
||||
Ensure `mod_headers` is enabled: `sudo a2enmod headers`
|
||||
|
||||
### Permission Denied
|
||||
|
||||
```bash
|
||||
|
||||
@@ -23,7 +23,8 @@ aws s3 website s3://your-bentopdf-bucket \
|
||||
## Step 2: Build and Upload
|
||||
|
||||
```bash
|
||||
# Build the project
|
||||
# Build the project (optionally with custom branding)
|
||||
# VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
npm run build
|
||||
|
||||
# Sync to S3
|
||||
@@ -56,6 +57,62 @@ Or use the AWS Console:
|
||||
4. Default root object: `index.html`
|
||||
5. Create distribution
|
||||
|
||||
## Step 3b: Response Headers Policy (Required for LibreOffice WASM)
|
||||
|
||||
LibreOffice-based conversions (Word, Excel, PowerPoint to PDF) require `SharedArrayBuffer`, which needs specific response headers. Create a CloudFront Response Headers Policy:
|
||||
|
||||
1. Go to CloudFront → Policies → Response headers
|
||||
2. Create a custom policy with these headers:
|
||||
|
||||
| Header | Value |
|
||||
| ------------------------------ | -------------- |
|
||||
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||
| `Cross-Origin-Resource-Policy` | `cross-origin` |
|
||||
|
||||
3. Attach the policy to your distribution's default cache behavior
|
||||
|
||||
Or via CLI:
|
||||
|
||||
```bash
|
||||
aws cloudfront create-response-headers-policy \
|
||||
--response-headers-policy-config '{
|
||||
"Name": "BentoPDF-COEP-COOP",
|
||||
"CustomHeadersConfig": {
|
||||
"Quantity": 3,
|
||||
"Items": [
|
||||
{"Header": "Cross-Origin-Embedder-Policy", "Value": "require-corp", "Override": true},
|
||||
{"Header": "Cross-Origin-Opener-Policy", "Value": "same-origin", "Override": true},
|
||||
{"Header": "Cross-Origin-Resource-Policy", "Value": "cross-origin", "Override": true}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Step 3c: S3 Metadata for Pre-Compressed WASM Files
|
||||
|
||||
The LibreOffice WASM files are pre-compressed (`.wasm.gz`, `.data.gz`). Set the correct Content-Type and Content-Encoding so browsers decompress them:
|
||||
|
||||
```bash
|
||||
# Set correct headers for soffice.wasm.gz
|
||||
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
|
||||
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
|
||||
--content-type "application/wasm" \
|
||||
--content-encoding "gzip" \
|
||||
--metadata-directive REPLACE
|
||||
|
||||
# Set correct headers for soffice.data.gz
|
||||
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
|
||||
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
|
||||
--content-type "application/octet-stream" \
|
||||
--content-encoding "gzip" \
|
||||
--metadata-directive REPLACE
|
||||
```
|
||||
|
||||
::: warning Important
|
||||
Without the response headers policy, `SharedArrayBuffer` is unavailable and LibreOffice WASM conversions will hang at ~55%. Without the correct Content-Encoding on the `.gz` files, the browser receives raw gzip bytes and WASM compilation fails.
|
||||
:::
|
||||
|
||||
## Step 4: S3 Bucket Policy
|
||||
|
||||
Allow CloudFront to access the bucket:
|
||||
@@ -94,11 +151,11 @@ Configure 404 to return `index.html` for SPA routing:
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
| Resource | Estimated Cost |
|
||||
|----------|----------------|
|
||||
| S3 Storage (~500MB) | ~$0.01/month |
|
||||
| CloudFront (1TB transfer) | ~$85/month |
|
||||
| CloudFront (10GB transfer) | ~$0.85/month |
|
||||
| Resource | Estimated Cost |
|
||||
| -------------------------- | -------------- |
|
||||
| S3 Storage (~500MB) | ~$0.01/month |
|
||||
| CloudFront (1TB transfer) | ~$85/month |
|
||||
| CloudFront (10GB transfer) | ~$0.85/month |
|
||||
|
||||
::: tip
|
||||
Use S3 Intelligent Tiering for cost optimization on infrequently accessed files.
|
||||
@@ -117,15 +174,15 @@ resource "aws_cloudfront_distribution" "bentopdf" {
|
||||
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
|
||||
origin_id = "S3Origin"
|
||||
}
|
||||
|
||||
|
||||
enabled = true
|
||||
default_root_object = "index.html"
|
||||
|
||||
|
||||
default_cache_behavior {
|
||||
allowed_methods = ["GET", "HEAD"]
|
||||
cached_methods = ["GET", "HEAD"]
|
||||
target_origin_id = "S3Origin"
|
||||
|
||||
|
||||
viewer_protocol_policy = "redirect-to-https"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,27 +10,49 @@
|
||||
|
||||
## Build Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Framework preset | None |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
| Setting | Value |
|
||||
| ---------------------- | --------------- |
|
||||
| Framework preset | None |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add these in Settings → Environment variables:
|
||||
|
||||
| Variable | Value |
|
||||
|----------|-------|
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false` (optional) |
|
||||
| Variable | Value |
|
||||
| ----------------------- | ------------------------------------------ |
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false` (optional) |
|
||||
| `VITE_BRAND_NAME` | Custom brand name (optional) |
|
||||
| `VITE_BRAND_LOGO` | Logo path relative to `public/` (optional) |
|
||||
| `VITE_FOOTER_TEXT` | Custom footer/copyright text (optional) |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Default UI language, e.g. `fr` (optional) |
|
||||
|
||||
## Configuration File
|
||||
|
||||
Create `_headers` in your `public` folder:
|
||||
|
||||
```
|
||||
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
|
||||
/*
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
|
||||
# Pre-compressed LibreOffice WASM binary
|
||||
/libreoffice-wasm/soffice.wasm.gz
|
||||
Content-Type: application/wasm
|
||||
Content-Encoding: gzip
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# Pre-compressed LibreOffice WASM data
|
||||
/libreoffice-wasm/soffice.data.gz
|
||||
Content-Type: application/octet-stream
|
||||
Content-Encoding: gzip
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# Cache WASM files aggressively
|
||||
/*.wasm
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
@@ -41,6 +63,10 @@ Create `_headers` in your `public` folder:
|
||||
Cache-Control: no-cache
|
||||
```
|
||||
|
||||
::: warning Important
|
||||
The `Cross-Origin-Embedder-Policy` and `Cross-Origin-Opener-Policy` headers are required for Word/ODT/Excel/PowerPoint to PDF conversions. Without them, `SharedArrayBuffer` is unavailable and the LibreOffice WASM engine will fail to initialize.
|
||||
:::
|
||||
|
||||
Create `_redirects` for SPA routing:
|
||||
|
||||
```
|
||||
@@ -89,11 +115,11 @@ npx wrangler deploy
|
||||
|
||||
### Security Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **URL Restrictions** | Only certificate URLs allowed |
|
||||
| **File Size Limit** | Max 10MB per request |
|
||||
| **Rate Limiting** | 60 req/IP/min (requires KV) |
|
||||
| Feature | Description |
|
||||
| ----------------------- | ------------------------------ |
|
||||
| **URL Restrictions** | Only certificate URLs allowed |
|
||||
| **File Size Limit** | Max 10MB per request |
|
||||
| **Rate Limiting** | 60 req/IP/min (requires KV) |
|
||||
| **Private IP Blocking** | Blocks localhost, internal IPs |
|
||||
|
||||
### Enable Rate Limiting
|
||||
|
||||
@@ -96,6 +96,9 @@ docker run -d -p 3000:8080 bentopdf:custom
|
||||
| `VITE_WASM_GS_URL` | Ghostscript WASM module URL | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/` |
|
||||
| `VITE_WASM_CPDF_URL` | CoherentPDF WASM module URL | `https://cdn.jsdelivr.net/npm/coherentpdf/dist/` |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Default UI language | `en` |
|
||||
| `VITE_BRAND_NAME` | Custom brand name | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Logo path relative to `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Custom footer/copyright text | `© 2026 BentoPDF. All rights reserved.` |
|
||||
|
||||
WASM module URLs are pre-configured with CDN defaults — all advanced features work out of the box. Override these for air-gapped or self-hosted deployments.
|
||||
|
||||
@@ -109,6 +112,20 @@ docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
|
||||
docker run -d -p 3000:8080 bentopdf
|
||||
```
|
||||
|
||||
### Custom Branding
|
||||
|
||||
Replace the default BentoPDF logo, name, and footer text with your own. Place your logo file in the `public/` folder (or use an existing image), then pass the branding variables at build time:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
Branding works in both full mode and Simple Mode, and can be combined with all other build-time options.
|
||||
|
||||
### Custom WASM URLs (Air-Gapped / Self-Hosted)
|
||||
|
||||
> [!IMPORTANT]
|
||||
|
||||
@@ -103,6 +103,36 @@ Deploy to a subdirectory:
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
### Custom Branding
|
||||
|
||||
Replace the default BentoPDF logo, name, and footer text with your own at build time:
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------ | ------------------------------------- | --------------------------------------- |
|
||||
| `VITE_BRAND_NAME` | Brand name shown in header and footer | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Logo path relative to `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Custom footer/copyright text | `© 2026 BentoPDF. All rights reserved.` |
|
||||
|
||||
```bash
|
||||
# Place your logo in public/, then build
|
||||
VITE_BRAND_NAME="AcmePDF" \
|
||||
VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
npm run build
|
||||
```
|
||||
|
||||
Or via Docker:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
Branding works in both full mode and Simple Mode, and can be combined with all other build-time options (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
|
||||
|
||||
## Deployment Guides
|
||||
|
||||
Choose your platform:
|
||||
@@ -166,6 +196,66 @@ Users can also override these defaults at any time via **Advanced Settings** in
|
||||
|
||||
For networks with no internet access (government, healthcare, financial, etc.). The WASM URLs are baked into the JavaScript at **build time** — the actual WASM files are downloaded by the **user's browser** at runtime. So you need to prepare everything on a machine with internet, then transfer it into the isolated network.
|
||||
|
||||
#### Automated Script (Recommended)
|
||||
|
||||
The included `prepare-airgap.sh` script automates the entire process — downloading WASM packages, building the Docker image, and producing a self-contained bundle with a setup script.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
|
||||
# Interactive mode — prompts for all options
|
||||
bash scripts/prepare-airgap.sh
|
||||
|
||||
# Or fully automated
|
||||
bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm
|
||||
```
|
||||
|
||||
This produces a bundle directory:
|
||||
|
||||
```
|
||||
bentopdf-airgap-bundle/
|
||||
bentopdf.tar # Docker image
|
||||
*.tgz # WASM packages (PyMuPDF, Ghostscript, CoherentPDF)
|
||||
setup.sh # Setup script for the air-gapped side
|
||||
README.md # Instructions
|
||||
```
|
||||
|
||||
Transfer the bundle into the air-gapped network via USB, internal artifact repo, or approved method. Then run the included setup script:
|
||||
|
||||
```bash
|
||||
cd bentopdf-airgap-bundle
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
The setup script loads the Docker image, extracts WASM files, and optionally starts the container.
|
||||
|
||||
**Script options:**
|
||||
|
||||
| Flag | Description | Default |
|
||||
| ----------------------- | ------------------------------------------------ | --------------------------------- |
|
||||
| `--wasm-base-url <url>` | Where WASMs will be hosted internally | _(required, prompted if missing)_ |
|
||||
| `--image-name <name>` | Docker image tag | `bentopdf` |
|
||||
| `--output-dir <path>` | Output bundle directory | `./bentopdf-airgap-bundle` |
|
||||
| `--simple-mode` | Enable Simple Mode | off |
|
||||
| `--base-url <path>` | Subdirectory base URL (e.g. `/pdf/`) | `/` |
|
||||
| `--language <code>` | Default UI language (e.g. `fr`, `de`) | _(none)_ |
|
||||
| `--brand-name <name>` | Custom brand name | _(none)_ |
|
||||
| `--brand-logo <path>` | Logo path relative to `public/` | _(none)_ |
|
||||
| `--footer-text <text>` | Custom footer text | _(none)_ |
|
||||
| `--dockerfile <path>` | Dockerfile to use | `Dockerfile` |
|
||||
| `--skip-docker` | Skip Docker build and export | off |
|
||||
| `--skip-wasm` | Skip WASM download (reuse existing `.tgz` files) | off |
|
||||
|
||||
::: warning Same-Origin Requirement
|
||||
WASM files must be served from the **same origin** as the BentoPDF app. Web Workers use `importScripts()` which cannot load scripts cross-origin. For example, if BentoPDF runs at `https://internal.example.com`, the WASM base URL should also be `https://internal.example.com/wasm`.
|
||||
:::
|
||||
|
||||
#### Manual Steps
|
||||
|
||||
<details>
|
||||
<summary>If you prefer to do it manually without the script</summary>
|
||||
|
||||
**Step 1: Download the WASM packages** (on a machine with internet)
|
||||
|
||||
```bash
|
||||
@@ -206,17 +296,19 @@ Copy via USB, internal artifact repo, or approved transfer method:
|
||||
# Load the Docker image
|
||||
docker load -i bentopdf.tar
|
||||
|
||||
# Extract WASM packages to your internal web server's document root
|
||||
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf
|
||||
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
|
||||
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
|
||||
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
|
||||
# Extract WASM packages
|
||||
mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf
|
||||
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1
|
||||
tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1
|
||||
tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1
|
||||
|
||||
# Run BentoPDF
|
||||
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
|
||||
```
|
||||
|
||||
Make sure the WASM files are accessible at the URLs you configured in Step 2. Users open their browser and everything works — no internet required.
|
||||
Make sure the WASM files are accessible at the URLs you configured in Step 2.
|
||||
|
||||
</details>
|
||||
|
||||
::: info Building from source instead of Docker?
|
||||
Set the variables in `.env.production` before running `npm run build`:
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
### Step 2: Configure Build Settings
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Build command | `npm run build` |
|
||||
| Publish directory | `dist` |
|
||||
| Node version | 18+ |
|
||||
| Setting | Value |
|
||||
| ----------------- | --------------- |
|
||||
| Build command | `npm run build` |
|
||||
| Publish directory | `dist` |
|
||||
| Node version | 18+ |
|
||||
|
||||
### Step 3: Deploy
|
||||
|
||||
@@ -39,27 +39,61 @@ Create `netlify.toml` in your project root:
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
|
||||
# SPA routing
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
Cross-Origin-Embedder-Policy = "require-corp"
|
||||
Cross-Origin-Opener-Policy = "same-origin"
|
||||
Cross-Origin-Resource-Policy = "cross-origin"
|
||||
|
||||
# Cache WASM files
|
||||
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
|
||||
[[headers]]
|
||||
for = "/libreoffice-wasm/soffice.wasm.gz"
|
||||
[headers.values]
|
||||
Content-Type = "application/wasm"
|
||||
Content-Encoding = "gzip"
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
|
||||
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
|
||||
[[headers]]
|
||||
for = "/libreoffice-wasm/soffice.data.gz"
|
||||
[headers.values]
|
||||
Content-Type = "application/octet-stream"
|
||||
Content-Encoding = "gzip"
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
|
||||
# Cache other WASM files
|
||||
[[headers]]
|
||||
for = "*.wasm"
|
||||
[headers.values]
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
Content-Type = "application/wasm"
|
||||
|
||||
# SPA routing
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
```
|
||||
|
||||
::: warning Important
|
||||
The `Cross-Origin-Embedder-Policy` and `Cross-Origin-Opener-Policy` headers are required for Word/ODT/Excel/PowerPoint to PDF conversions. Without them, `SharedArrayBuffer` is unavailable and the LibreOffice WASM engine will fail to initialize.
|
||||
|
||||
The `Content-Encoding: gzip` headers on the `.wasm.gz` and `.data.gz` files tell the browser to decompress them automatically. Without these, the browser receives raw gzip bytes and WASM compilation fails.
|
||||
:::
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set these in Site settings → Environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `SIMPLE_MODE` | Set to `true` for minimal build |
|
||||
| Variable | Description |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Set to `true` for minimal build |
|
||||
| `VITE_BRAND_NAME` | Custom brand name (replaces "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Logo path relative to `public/` (e.g. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Custom footer/copyright text |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Default UI language (e.g. `fr`, `de`, `es`) |
|
||||
|
||||
## Custom Domain
|
||||
|
||||
@@ -78,6 +112,19 @@ git lfs track "*.wasm"
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Word/ODT/Excel to PDF Stuck at 55%
|
||||
|
||||
If document conversions hang at 55%, open DevTools Console and check:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
If `crossOriginIsolated` is `false`, the COEP/COOP headers from your `netlify.toml` are not being applied. Make sure the file is in your project root and redeploy.
|
||||
|
||||
If you see `expected magic word 00 61 73 6d, found 1f 8b 08 08` in the console, the `.wasm.gz` files are missing `Content-Encoding: gzip` headers. Ensure the `[[headers]]` blocks for `soffice.wasm.gz` and `soffice.data.gz` are in your `netlify.toml`.
|
||||
|
||||
### Build Fails
|
||||
|
||||
Check Node version compatibility:
|
||||
|
||||
@@ -17,6 +17,12 @@ npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
To customize branding, set environment variables before building:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Step 2: Copy Files
|
||||
|
||||
```bash
|
||||
@@ -57,21 +63,51 @@ server {
|
||||
application/wasm wasm;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# SPA routing - serve index.html for all routes
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
# Required headers for SharedArrayBuffer (LibreOffice WASM)
|
||||
# These must be on every response - especially HTML pages
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Resource-Policy "cross-origin" always;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Pre-compressed LibreOffice WASM binary
|
||||
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
|
||||
gzip off;
|
||||
types {} default_type application/wasm;
|
||||
add_header Content-Encoding gzip;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
}
|
||||
|
||||
# Pre-compressed LibreOffice WASM data
|
||||
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
|
||||
gzip off;
|
||||
types {} default_type application/octet-stream;
|
||||
add_header Content-Encoding gzip;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
}
|
||||
|
||||
# SPA routing - serve index.html for all routes
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -120,7 +156,7 @@ http {
|
||||
|
||||
# Increase buffer sizes
|
||||
client_max_body_size 100M;
|
||||
|
||||
|
||||
# Worker connections
|
||||
worker_connections 2048;
|
||||
}
|
||||
@@ -138,6 +174,18 @@ types {
|
||||
}
|
||||
```
|
||||
|
||||
### Word/ODT/Excel to PDF Not Working
|
||||
|
||||
LibreOffice WASM requires `SharedArrayBuffer`, which needs `Cross-Origin-Embedder-Policy` and `Cross-Origin-Opener-Policy` headers. Note that nginx `add_header` directives in a `location` block **override** server-level `add_header` directives — they don't merge. Every `location` block with its own `add_header` must include the COEP/COOP headers.
|
||||
|
||||
Verify with:
|
||||
|
||||
```bash
|
||||
curl -I https://your-domain.com/ | grep -i cross-origin
|
||||
```
|
||||
|
||||
If using a reverse proxy in front of nginx, ensure it preserves these headers.
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
Check Nginx error logs:
|
||||
|
||||
@@ -18,20 +18,24 @@ Fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) to your GitHub a
|
||||
2. Select your forked repository
|
||||
3. Configure the project:
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
| Setting | Value |
|
||||
| ---------------- | --------------- |
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
|
||||
### Step 3: Environment Variables (Optional)
|
||||
|
||||
Add these if needed:
|
||||
|
||||
```
|
||||
SIMPLE_MODE=false
|
||||
```
|
||||
| Variable | Description |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Set to `true` for minimal UI |
|
||||
| `VITE_BRAND_NAME` | Custom brand name (replaces "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Logo path relative to `public/` (e.g. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Custom footer/copyright text |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Default UI language (e.g. `fr`, `de`, `es`) |
|
||||
|
||||
### Step 4: Deploy
|
||||
|
||||
@@ -73,3 +77,46 @@ Add a `vercel.json` for SPA routing:
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Word/ODT/Excel to PDF Not Working
|
||||
|
||||
LibreOffice WASM conversions require `SharedArrayBuffer`, which needs these response headers on all pages:
|
||||
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
|
||||
The pre-compressed `.wasm.gz` and `.data.gz` files also need `Content-Encoding: gzip` so the browser decompresses them.
|
||||
|
||||
Add these to your `vercel.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
|
||||
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
|
||||
{ "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/libreoffice-wasm/soffice.wasm.gz",
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": "application/wasm" },
|
||||
{ "key": "Content-Encoding", "value": "gzip" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/libreoffice-wasm/soffice.data.gz",
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": "application/octet-stream" },
|
||||
{ "key": "Content-Encoding", "value": "gzip" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To verify, open DevTools Console and run `console.log(window.crossOriginIsolated)` — it should return `true`.
|
||||
|
||||
Reference in New Issue
Block a user