back to all notes

infrastructure2 min read

Cloudflare Edge Router for Multi-Project Documentation

I wanted the documentation for several Apptork Labs projects under one subdomain, rather than a separate subdomain for each repository. A small Cloudflare Worker routes requests to the relevant Cloudflare Pages site.

Requests to docs.apptork.com/backend/, /mobile/, and /frontend/ go to their respective .pages.dev hosts. The Worker takes the first path segment, looks up an environment variable such as URL_BACKEND, strips the project prefix, and fetches the remaining path from that host.

At /, it returns a page linking to the projects. It redirects /backend to /backend/ so relative links resolve from the project directory. Unknown project names return a 404 after any base-path redirect.

Configuration

Set the public base URL in each project's mkdocs.yml:

site_url: "https://docs.apptork.com/backend/"

The URL describes where the documentation is served. Asset and link behavior still depends on MkDocs and the theme; this setting does not guarantee that every generated URL is absolute. Check the generated pages at the public subpath.

Bind the Worker to docs.apptork.com using Cloudflare's Custom Domains feature. Set the upstream environment variables to hostnames, for example URL_BACKEND = backend-boilerplate.pages.dev. Adding a variable lets the same lookup route another prefix, though the landing-page links still need updating.

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const path = url.pathname;

    // Extract the first path segment (e.g., 'backend' from '/backend/assets/...')
    const project = path.split('/')[1]; 

    if (!project) {
      const html = `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Apptork Docs Gateway</title>
        <meta name="description" content="Official documentation portal for Apptork boilerplates. Production-ready backends, frontends, and mobile ecosystems.">
        <meta name="robots" content="index, follow">
      </head>
      <body style="font-family: monospace; padding: 3rem; background: #0a0a0a; color: #eaeaea; max-width: 600px; margin: 0 auto;">
        <h2>Apptork Documentation Gateway</h2>
        <ul style="line-height: 2.5; list-style-type: square;">
          <li><a href="/backend/" style="color: #4ade80; text-decoration: none;">Backend Boilerplate</a></li>
          <li><a href="/frontend/" style="color: #4ade80; text-decoration: none;">Frontend Boilerplate</a></li>
          <li><a href="/mobile/" style="color: #4ade80; text-decoration: none;">Mobile Boilerplate</a></li>
        </ul>
      </body>
      </html>
      `;
      return new Response(html, { status: 200, headers: { "Content-Type": "text/html" } });
    }

    // Force trailing slash for the base project URL to ensure relative CSS/JS paths resolve correctly
    if (path === `/${project}`) {
      return Response.redirect(`${url.origin}/${project}/${url.search}`, 301);
    }

    // Dynamically resolve target hostname via Environment Variables
    const targetHost = env[`URL_${project.toUpperCase()}`];

    if (targetHost) {
      const targetUrl = new URL(request.url);
      targetUrl.hostname = targetHost;
      
      // Strip the top-level identifier to route to the Pages root
      targetUrl.pathname = targetUrl.pathname.substring(`/${project}`.length);
      
      return fetch(new Request(targetUrl, request));
    }

    return new Response('Target repository not found.', { status: 404 });
  }
};

Skip unrelated documentation builds

Cloudflare Pages' Build watch paths can limit rebuilds to documentation changes. The include paths used here are:

 Build watch paths
 Include paths:
 docs/*
 docs-requirements.txt
 mkdocs.yml

These are dashboard settings, not a shell command whose exit code cancels a build. A pre-build Git diff check is a separate approach; this example does not include that script. Check the path-pattern behavior for nested files and include any other inputs the docs build depends on.

This note does not include latency measurements or tests of redirects and links returned by the upstream sites. The Worker rewrites the request URL; it does not rewrite upstream response bodies or Location headers.