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

  1. Overview
  2. How Stripping Works
  3. The Problem with URLs
  4. Strategies for Handling URLs
  5. Headers Reference
  6. Deployment Environments
  7. 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 requestsYour 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:

  1. Path rewrite: removes the prefix from the request URL
  2. Header injection: adds X-Forwarded-Prefix so 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: /login

The 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:

FrameworkConfiguration
Next.jsbasePath: '/app' in next.config.js
FlaskAPPLICATION_ROOT = '/app' or url_prefix='/app' on blueprints
Expressapp.use('/app', router)
DjangoFORCE_SCRIPT_NAME = '/app'
Spring Bootserver.servlet.context-path=/app
Railsconfig.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: /app

Example 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:

FrameworkSupport
Spring Bootserver.forward-headers-strategy=framework (reads X-Forwarded-Prefix)
DjangoUSE_X_FORWARDED_PREFIX = True (Django 4.0+)
ASP.NET CoreForwardedHeadersOptions with ForwardedHeaders.XForwardedPrefix
LaravelRequires 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:

HeaderSet byValuePurpose
X-Forwarded-PrefixPlatformThe stripped path prefix (e.g., /app)Lets the app reconstruct the original URL path
X-Forwarded-HostTraefikOriginal Host header from the clientUseful when the backend sees a different internal hostname
X-Forwarded-ProtoTraefikhttp or httpsOriginal scheme, important for generating secure URLs
X-Forwarded-ForTraefikClient IP addressOriginal client IP, important for rate limiting and logging
X-Real-IpTraefikClient IP addressAlternative 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-headers plugin 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-Proto reflects the actual client scheme (typically https)
  • Location-header rewriting is still not performed here either — it needs the Traefik rewrite-headers plugin, 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:

  1. Path prefix matching and routing
  2. Prefix stripping via middleware
  3. X-Forwarded-Prefix header injection
  4. 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.js or ./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.