Path Prefixes
Audience: Application creators deploying apps behind a path prefix on the WBSP platform.
Prerequisite: You should be familiar with the Application Creator Guide, particularly the Configuration and Route Entry sections.
Table of Contents
- Overview
- How Stripping Works
- The Problem with URLs
- Strategies for Handling URLs
- Headers Reference
- Deployment Environments
- Troubleshooting
Overview
When you deploy an application with a path_prefix, the platform routes requests matching that prefix to your application. By default, the prefix is stripped before the request reaches your app — your application sees clean paths as if it were mounted at the root.
destination:
prod:
type: aws
tenant: acme
cluster: main
enclave: mysite
access: public
routes:
prod: { domain: mysite.example.com, path_prefix: /app }With this configuration:
| Client requests | Your app receives |
|---|---|
/app/dashboard | /dashboard |
/app/api/users | /api/users |
/app | / |
This means your application does not need any special path configuration to handle requests. It works the same whether deployed at /, /app, or /tools/v2/app.
Disabling Stripping
If your application already handles the prefix internally (e.g., via a framework's base path setting), you can disable stripping:
destination:
prod:
type: aws
tenant: acme
cluster: main
enclave: mysite
access: public
routes:
prod: { domain: mysite.example.com, path_prefix: /app, strip_prefix: false }With strip_prefix: false, your app receives the full original path (/app/dashboard), and no forwarding headers are added.
How Stripping Works
The platform's ingress layer (Traefik) intercepts incoming requests, matches them against your route's path_prefix, and applies two transformations before forwarding:
- Path rewrite: removes the prefix from the request URL
- Header injection: adds
X-Forwarded-Prefixso your app knows the original prefix
Query strings, request bodies, HTTP methods, and all other headers pass through unchanged. This applies identically in both container deployment and dev mode.
The Problem with URLs
Stripping solves inbound routing cleanly, but outbound URLs — links, redirects, and API responses your app sends back to the client — can break if they don't account for the prefix.
Consider an app deployed at /app that returns:
HTTP/1.1 302 Found
Location: /loginThe client browser follows the redirect to /login, which is outside the prefix — it should have been /app/login. The same issue applies to:
- HTML links (
<a href="/dashboard">) - API responses containing URLs (
{"next": "/api/users?page=2"}) - Form actions (
<form action="/submit">) - Asset paths (
<script src="/js/app.js">)
This is a fundamental challenge with path-prefix routing, not specific to this platform. There are three strategies for handling it.
Strategies for Handling URLs
Strategy 1: Do Not Strip the Prefix
The simplest option if your framework supports a base path or URL prefix setting.
destination:
prod:
type: aws
tenant: acme
cluster: main
enclave: mysite
access: public
routes:
prod: { domain: mysite.example.com, path_prefix: /app, strip_prefix: false }Your application receives the full path and is responsible for handling it. Most frameworks have built-in support:
| Framework | Configuration |
|---|---|
| Next.js | basePath: '/app' in next.config.js |
| Flask | APPLICATION_ROOT = '/app' or url_prefix='/app' on blueprints |
| Express | app.use('/app', router) |
| Django | FORCE_SCRIPT_NAME = '/app' |
| Spring Boot | server.servlet.context-path=/app |
| Rails | config.relative_url_root = '/app' |
When to use: your app is always deployed at a known, fixed prefix and you want full control over URL generation.
Trade-off: your app is coupled to the prefix. Moving it to a different path means changing configuration and redeploying.
Strategy 2: Use Relative URLs
If all URLs your application generates are relative to the current page, path prefixes are invisible — the browser resolves them correctly regardless of where the app is mounted.
<!-- Instead of this (absolute, breaks with prefix): -->
<a href="/dashboard">Dashboard</a>
<!-- Use this (relative, works everywhere): -->
<a href="dashboard">Dashboard</a>For API responses, return relative paths or let the client construct URLs based on the current location.
When to use: new applications or apps you control end-to-end. This is the most portable approach.
Trade-off: not all frameworks default to relative URLs. Redirect responses (Location header) are particularly tricky — most frameworks generate absolute paths by default. Asset bundlers and CDN paths may also need adjustment.
Strategy 3: Use Forwarding Headers
When stripping is enabled, the platform injects an X-Forwarded-Prefix header on every request. Your application reads this header and prepends it when generating absolute URLs.
X-Forwarded-Prefix: /appExample in Express:
app.use((req, res, next) => {
req.baseUrl = req.headers['x-forwarded-prefix'] || '';
next();
});
app.get('/login', (req, res) => {
// Redirect uses the prefix
res.redirect(req.baseUrl + '/dashboard');
});Example in Python (Flask):
from flask import request, redirect
@app.before_request
def set_prefix():
request.prefix = request.headers.get('X-Forwarded-Prefix', '')
@app.route('/login')
def login():
return redirect(request.prefix + '/dashboard')Many frameworks have middleware or configuration that reads X-Forwarded-Prefix automatically:
| Framework | Support |
|---|---|
| Spring Boot | server.forward-headers-strategy=framework (reads X-Forwarded-Prefix) |
| Django | USE_X_FORWARDED_PREFIX = True (Django 4.0+) |
| ASP.NET Core | ForwardedHeadersOptions with ForwardedHeaders.XForwardedPrefix |
| Laravel | Requires custom TrustedProxy configuration |
When to use: your app needs stripping (portability across prefixes) but also generates absolute URLs or redirects.
Trade-off: requires application-level awareness of the header. Not all frameworks support it out of the box.
Headers Reference
When strip_prefix is true (the default) and path_prefix is not /, the platform sets the following headers on forwarded requests:
| Header | Set by | Value | Purpose |
|---|---|---|---|
X-Forwarded-Prefix | Platform | The stripped path prefix (e.g., /app) | Lets the app reconstruct the original URL path |
X-Forwarded-Host | Traefik | Original Host header from the client | Useful when the backend sees a different internal hostname |
X-Forwarded-Proto | Traefik | http or https | Original scheme, important for generating secure URLs |
X-Forwarded-For | Traefik | Client IP address | Original client IP, important for rate limiting and logging |
X-Real-Ip | Traefik | Client IP address | Alternative to X-Forwarded-For, used by some frameworks |
The Location Header
The Location response header is used by your application to issue HTTP redirects (301, 302, 307, 308). When your app returns Location: /login but is deployed at /app, the client ends up at the wrong URL.
The platform does not currently rewrite Location headers in responses. This is a known limitation — it requires a Traefik plugin that is not yet configured. Until then, use one of the three strategies above to ensure your redirects include the correct prefix.
CORS Considerations
If your application handles CORS (Cross-Origin Resource Sharing), the stripping process does not interfere — the Origin, Access-Control-Request-Method, and Access-Control-Request-Headers request headers are forwarded unmodified. However, ensure that your CORS Access-Control-Allow-Origin response uses the external origin (which includes the prefix path) and not the internal stripped URL.
Deployment Environments
Path prefix stripping is implemented at the ingress layer, which varies by deployment environment. The behavior described in this guide applies across all environments, but the underlying mechanism differs.
Local (Docker)
Status: Fully implemented.
The local environment uses Traefik v2 running as a Docker container. Stripping is configured via Docker labels on your application's container. The StripPrefix middleware removes the prefix, and a headers middleware injects X-Forwarded-Prefix.
This works identically for both container deployments and dev mode (where traffic is proxied to your local machine via socat).
Limitations:
- Location header rewriting is not available (Traefik v2 does not support conditional response header modification via Docker labels)
- Upgrading to Traefik v3 or adding the
traefik-plugin-rewrite-headersplugin would enable this in the future
AWS (Kubernetes)
Status: Implemented.
The AWS environment uses Traefik IngressRoute CRDs in Kubernetes. The same
StripPrefix middleware and X-Forwarded-Prefix header injection described above
are configured via the IngressRoute/middleware CRDs rather than Docker labels, so
path-prefix stripping behaves identically to local.
Differences from local:
- TLS termination happens at the load balancer / ingress, so
X-Forwarded-Protoreflects the actual client scheme (typicallyhttps) Location-header rewriting is still not performed here either — it needs the Traefikrewrite-headersplugin, which is not yet loaded (see The Location Header). Use one of the three strategies above for correct redirects.
GCP (Kubernetes)
Status: Planned — not a current deployment target.
GCP is not offered as a deployment target today; the notes below describe the intended design if it is added. It would follow the same pattern as AWS using Traefik IngressRoute CRDs, with these GCP-specific considerations:
- If using Google Cloud Load Balancer in front of Traefik,
X-Forwarded-*headers may already be set by the load balancer — the platform should append rather than overwrite - GCP's Identity-Aware Proxy (IAP), if used, adds its own headers that must not be stripped
Other Environments
Future deployment targets will implement the same ingress Provider interface. Path prefix stripping behavior will be consistent, though the underlying mechanism (labels, annotations, config files) will vary. Each new provider must support:
- Path prefix matching and routing
- Prefix stripping via middleware
X-Forwarded-Prefixheader injection- Passthrough of all other headers, query strings, and request bodies
Troubleshooting
My app gets the full path including the prefix
Check that strip_prefix is not set to false in your route config. If omitted, it defaults to true.
Redirects go to the wrong URL
Your app is generating absolute redirect URLs without the prefix. Use one of the strategies above — reading X-Forwarded-Prefix or switching to relative URLs are the most common fixes.
Static assets (CSS, JS, images) fail to load
If your HTML references assets with absolute paths (/js/app.js), the browser requests them outside the prefix. Options:
- Use relative paths (
js/app.jsor./js/app.js) - Set your framework's asset prefix/public path to read from
X-Forwarded-Prefix - Use a
<base>tag in your HTML:<base href="/app/">
X-Forwarded-Prefix header is missing
This header is only set when strip_prefix is true (or defaulted) and path_prefix is not /. If your app is at the root, there is no prefix to forward.
My framework ignores X-Forwarded-Prefix
Not all frameworks read this header by default. Check the framework support table and configure your framework accordingly. If your framework has no support, add middleware that reads the header and prepends it to generated URLs.