Service bindings

The Website talks to the Platform Worker (swapps-worker) through a Cloudflare Service Binding named API_WORKER. This is a direct Worker-to-Worker call: it never leaves Cloudflare's network, has no public HTTP hop, and incurs no egress.

Declaration

The binding is declared in wrangler.toml for the default, preview, and dev environments:

[[services]]
binding = "API_WORKER"
service = "swapps-worker"

At runtime it is exposed on the Worker env as env.API_WORKER, a Fetcher with a single fetch(request) method.

How a request reaches the binding during SSR

src/worker.ts passes the binding into the Vike page context so that server-side +data.ts loaders can use it while rendering:

const pageContextInit = {
  urlOriginal: url.href,
  headersOriginal: Object.fromEntries(request.headers),
  env: { BASE_URL, API_URL, NODE_ENV },
  cloudflare: {
    env: {
      API_WORKER: env.API_WORKER,
    },
  },
};

The fetchData() helper and the HTTP fallback

All API calls go through fetchData() in src/utils/api.ts, which chooses the transport based on what is available:

export async function fetchData(url, options = {}) {
  const isDocker = import.meta.env.VITE_IS_DOCKER === "true";
  const { pageContext, ...fetchOptions } = options;

  if (isDocker) {
    // Local Docker: plain HTTP to API_URL
    return fetch(url, fetchOptions);
  } else if (pageContext?.cloudflare?.env?.API_WORKER) {
    // Production / preview: zero-latency Worker-to-Worker
    const request = new Request(url, fetchOptions);
    return pageContext.cloudflare.env.API_WORKER.fetch(request);
  } else {
    // Fallback: plain HTTP (e.g. binding unavailable)
    console.error("Cloudflare API_WORKER not found. Falling back to standard fetch.");
    return fetch(url, fetchOptions);
  }
}

Resolution order:

Condition Transport
VITE_IS_DOCKER === "true" Plain fetch() to API_URL (local Docker)
pageContext.cloudflare.env.API_WORKER present Service Binding API_WORKER.fetch()
Neither Plain fetch() fallback (logs an error)

Note

Even on the binding path the call uses the full URL (built from __API_URL__). The binding routes the request straight to swapps-worker without a public DNS/HTTP round-trip, so the host portion is effectively internal. Locally, API_URL points at http://localhost:8787 so the same code path falls back to HTTP against a locally running Platform Worker.

What flows over the binding

Server-side +data.ts loaders call Platform endpoints through fetchData(..., { pageContext }):

Use Example endpoint (on the Platform Worker)
Blog listing /api/posts?per_page=…&page=…&lang=…
Blog tags /api/posts/tags?…
Blog categories /api/posts/categories?…
Lead submission /api/leads
Diagnostic / SEO analysis /api/diagnostic/analyze, /api/seo/analyze
Support contact /api/support/send

Warning

Calls that originate client-side (after hydration, e.g. form submissions triggered by the browser) cannot use the Service Binding — the binding only exists inside the Worker. Those go out as normal HTTPS requests to the Platform Worker's public URL (__API_URL__).

Two other services are reached with plain fetch, not Service Bindings:

  • Appfetch to SUBSCRIPTIONS_API_URL (app.swapps.com) for subscription plans and checkout (src/api/subscriptions/).
  • AIfetch to /api/ai/* on the same origin, routed by Cloudflare to the swapps-ai Worker (src/api/ai.ts). See Lead generation.

Next steps