feat(docs): add reverse proxy setup guide with provider-specific instructions#5601
Conversation
…ructions Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| A working reverse proxy does two things: | ||
|
|
||
| 1. **Routes requests** from your subpath to Fern's origin, with the `x-fern-host` header set so Fern identifies your site. | ||
| 2. **Disables caching** of HTML responses so visitors always receive the latest deployment. |
There was a problem hiding this comment.
[FernStyles.Current] Avoid time-relative terms like 'latest' that become outdated
| - Cache policy: `CachingDisabled` (AWS managed policy) | ||
| - Origin request policy: `AllViewer` (forwards all headers, query strings, and cookies) | ||
|
|
||
| If you need fine-grained control instead of `CachingDisabled`, create a custom cache policy with TTL values set to `0` and forward the `Host` and `x-fern-host` headers. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| ``` | ||
|
|
||
| <Warning> | ||
| Nginx does not natively support [Brotli](https://github.com/google/brotli) decompression. The `Accept-Encoding` override above prevents HTTP/2 transfer errors caused by Fern's default Brotli-compressed responses. |
There was a problem hiding this comment.
🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'doesn't' instead of 'does not'.
|
|
||
| ## Caching | ||
|
|
||
| Fern sets `Cache-Control: public, max-age=0, must-revalidate` on HTML responses, which tells your proxy not to cache page content. Most providers respect this header by default. However, if your provider overrides origin cache headers or applies a default TTL, you must explicitly disable caching for the proxied path. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| Caching stale HTML causes visitors to load a page that references JavaScript and CSS files from an older deployment. Those files no longer exist, so the page fails to hydrate and may display an error. | ||
|
|
||
| <Warning> | ||
| Do not cache HTML responses from Fern. Static assets (`/_next/static/*`) are served directly by Fern's CDN with long-lived cache headers and do not pass through your proxy. |
There was a problem hiding this comment.
🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'don't' instead of 'Do not'.
| | Setting | Value | | ||
| |---|---| | ||
| | Minimum TTL | `0` | | ||
| | Maximum TTL | `0` | |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| |---|---| | ||
| | Minimum TTL | `0` | | ||
| | Maximum TTL | `0` | | ||
| | Default TTL | `0` | |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| | Default TTL | `0` | | ||
|
|
||
| <Warning> | ||
| CloudFront ignores `CDN-Cache-Control` and `Surrogate-Control` headers. It only reads the standard `Cache-Control` header. A custom cache policy with a non-zero `default-TTL` will cache responses regardless of the origin's `Cache-Control: max-age=0` directive. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| </Tab> | ||
| <Tab title="Caddy"> | ||
|
|
||
| Caddy does not cache responses by default. No additional configuration is needed unless you have explicitly enabled caching with a `cache` directive or external module. |
There was a problem hiding this comment.
🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'doesn't' instead of 'does not'.
|
|
||
| ## Verify your setup | ||
|
|
||
| After configuring your proxy, confirm that requests are routed correctly and responses are not cached. |
There was a problem hiding this comment.
🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'aren't' instead of 'are not'.
|
🌿 Preview your docs: https://fern-preview-devin-1779467679-reverse-proxy-docs.docs.buildwithfern.com/learn Here are the markdown pages you've updated: |
Co-Authored-By: Sandeep Dinesh <sandeep@buildwithfern.com>
| - Cache policy: `CachingDisabled` (AWS managed policy) | ||
| - Origin request policy: `AllViewer` (forwards all headers, query strings, and cookies) | ||
|
|
||
| If you need fine-grained control instead of `CachingDisabled`, create a custom cache policy with time-to-live (TTL) values set to `0` and forward the `Host` and `x-fern-host` headers. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
|
|
||
| Use the AWS managed `CachingDisabled` cache policy on the `/docs*` behavior. This policy sets `min-TTL`, `max-TTL`, and `default-TTL` to `0`, so CloudFront always fetches a fresh response from Fern. | ||
|
|
||
| If you use a custom cache policy instead, set all time-to-live (TTL) values to `0`: |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
Co-Authored-By: Anar Kafkas <anarkafkas@gmail.com>
|
|
||
| Set the behavior to: | ||
|
|
||
| - **Origin**: the Fern origin you just created |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Hedges] Avoid hedge words and filler like 'just'. Prefer direct statements.
| - **Cache policy**: `CachingDisabled` (AWS managed policy) | ||
| - **Origin request policy**: `AllViewer` (forwards all headers, query strings, and cookies) | ||
|
|
||
| To use a custom cache policy instead of `CachingDisabled`, set min, max, and default TTL to `0` and forward `Host` and `x-fern-host`. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
| </Steps> | ||
|
|
||
| <Warning> | ||
| CloudFront ignores `CDN-Cache-Control` and `Surrogate-Control` — only the standard `Cache-Control` header is read. A custom cache policy with a non-zero default TTL caches responses regardless of Fern's `Cache-Control: max-age=0` directive. |
There was a problem hiding this comment.
📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'TTL' has no definition.
…proxy guide - Vercel: Remove incorrect 'has' conditions from rewrites (has is a match condition, not a header-setter). Add middleware example for x-fern-host. - Caddy: Change handle_path to handle to preserve the /docs prefix in the upstream request. Co-Authored-By: Anar Kafkas <anarkafkas@gmail.com>
Replace rewrites + middleware approach with routes + transforms, which handles both the rewrite and the header in one vercel.json config. Co-Authored-By: Anar Kafkas <anarkafkas@gmail.com>
Summary
Adds a new documentation page explaining how to configure a reverse proxy for serving Fern docs from a subpath (e.g.
mydomain.com/docs). Covers:x-fern-hostandHostheaders so Fern identifies the correct docs siteProvider-specific instructions for: Cloudflare Workers, AWS CloudFront, Netlify, Vercel, Nginx, Akamai, and Caddy.
Also updates the existing "Setting up your domain" page to cross-reference the new reverse proxy page from the subpath setup section.
Review & Testing Checklist for Human
/learn/docs/preview-publish/reverse-proxyin the preview deploymentx-fern-hostheader documentation matches current Fern behavior (header value is bare domain without subpath)Notes
The origin URL used in all examples is
app.buildwithfern.com, which is the production Fern docs origin. Thex-fern-hostheader behavior is documented inpackages/fern-docs/bundle/src/proxy.tsandpackages/commons/docs-server/src/xfernhost/edge.tsin the fern-platform repo.Link to Devin session: https://app.devin.ai/sessions/5705640d594a45858652bac81f125c9d
Requested by: @thesandlord