Skip to content

Repository files navigation

Path-Based Application Router

This Cloudflare Worker provides a way to serve multiple applications under different paths of a single domain, while maintaining separate deployments. It supports both proxy routing and HTTP redirects.

graph LR
    subgraph "Your Domain"
        R[mydomain.com]
    end

    subgraph "Requests"
        R --"/app1/*"--> A[app1.example.com]
        R --"/app2/*"--> B[app2.example.com]
        R --"/blog/*"--> C[blog.example.com]
        R --"/"--------> D[www​.example.com]
    end

    style R fill:#f9f,stroke:#333
    style A fill:#9cf,stroke:#333
    style B fill:#9cf,stroke:#333
    style C fill:#9cf,stroke:#333
    style D fill:#fc9,stroke:#333
Loading

I use this worker to serve multiple applications under tools.osteele.com:

const ROUTES = {
   '/claude-artifact-viewer/*': 'proxy:https://claude-artifact-viewer.underconstruction.fun/*',
  "/shutterspeak/*": "proxy:https://shutterspeak.underconstruction.fun/*",
  '/tone-curve/*': 'proxy:https://tone-curve.underconstruction.fun/*',
  '/': 'https://osteele.com/tools'
};

This lets me maintain separate deployments for each tool while serving them under my tools.osteele.com subdomain.

Installation and Usage

  1. Create a new Cloudflare Worker project:
npm create cloudflare@latest
  1. Install this package:
bun add cf-path-router
# or: npm install cf-path-router
  1. Create a src/worker.ts file with your routing configuration:
import { createRouter } from 'cf-path-router';
const ROUTES = {
  '/app1/*': 'proxy:https://app1.example.com/*',
  '/app2/*': 'proxy:https://app2.example.com/*',
  '/': 'https://example.com'
};
export default createRouter(ROUTES);
  1. Update your wrangler.toml:
name = "path-router"
main = "src/worker.ts"
compatibility_date = "2024-01-01"
  1. Deploy to Cloudflare:
wrangler deploy

After deployment, requests to your worker's domain will be routed according to your configuration:

  • your-worker.example.com/app1/pageapp1.example.com/page
  • your-worker.example.com/app2/pathapp2.example.com/path
  • your-worker.example.com/ → redirects to example.com

Use Cases

  1. Multiple Apps, Single Domain: Serve multiple independently deployed applications under a single domain:

    • mydomain.com/app1app1.otherdomain.com
    • mydomain.com/app2app2.otherdomain.com
  2. Path-Based Routing: Route specific paths to specific locations:

    • mydomain.com/tools/xtools.otherdomain.com/x
    • mydomain.com/appotherdomain.com/app
  3. HTTP Redirects: Redirect specific paths to external URLs:

    • mydomain.com/302 redirect to otherdomain.com/tools
  4. Pass-through Routing: Any paths not explicitly configured pass through to normal Cloudflare handling.

Configuration

Configuration Format

The worker uses a simple routing table:

const ROUTES = {
  // Proxy routes - use /* to indicate subpath matching
  '/apps/calculator/*': 'proxy:https://calc.example.com/*',
  '/blog/*': 'proxy:https://blog.example.com/*',

  // HTTP redirect - exact path match (no wildcard)
  '/': 'https://www.example.com',
  '/about': 'https://about.example.com'
};

Each entry maps a path to either:

  • A proxy route (proxy: prefix and /* suffix) - requests will be proxied, preserving additional path segments
  • A redirect route (no prefix, no wildcard) - requests will receive a 302 redirect, exact path match only

When multiple routes match, exact routes take precedence. Otherwise, the router uses the wildcard route with the longest matching prefix.

How It Works

  1. Path Matching: When a request comes in, the worker checks the path against configured routes.
flowchart LR
    A[Request] --> B{Match Route?}
    B -->|Yes| C[Apply Route]
    B -->|No| D[Pass Through]
    C --> E[Proxy Request]
    C --> F[Redirect]
Loading
  1. URL Handling:

    # Proxy Route Example:
    your-worker.com/app1/page?q=123   →   app1.example.com/page?q=123
    └─── match ───┘└── subpath ──┘└─q─┘   └─── target ───┘└─same─┘└─q─┘
    
    # Redirect Example:
    your-worker.com/tools   →   302 →   tools.example.com
    └─── exact match ──┘              └─── destination ───┘
    
  2. Pass-through: If no match is found, the request passes through to normal Cloudflare handling

  3. Headers:

    flowchart TD
        A[Request] --> B{Content Type?}
        B -->|HTML| C[No Cache]
        B -->|Static Asset| D[Long Cache]
        B -->|Other| E[Default Cache]
        C & D & E --> F[Send Response]
    
    Loading

Client Application Requirements

Applications being served through this worker should:

  1. Use relative paths for assets and navigation (e.g., "styles.css" instead of "/styles.css")
  2. Be built without a hardcoded base path

For Vite applications, this can be achieved with:

// vite.config.js
export default defineConfig({
  base: '', // Empty string forces relative paths
})

Development

This project uses Bun for development and testing. Make sure you have Bun installed:

curl -fsSL https://bun.sh/install | bash

Install dependencies:

bun install

Run development server:

bun run dev

Run tests:

bun test

Type checking:

bun run type-check

Notes

  • The worker preserves configured and incoming query parameters; incoming values override configured values with the same name
  • Error handling is included for failed requests
  • Caching is configured appropriately for different content types
  • Host headers are managed to avoid conflicts

License

MIT License. Copyright © 2024 Oliver Steele. See LICENSE for details.

About

A Cloudflare Worker for path-based application routing

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages