anomalyco/sst · error

In "${name}" Router, the route path "${path}" can only have

Error message

In "${name}" Router, the route path "${path}" can only have one of url or bucket

What it means

Each inline route must resolve to exactly one target kind: a `url` (string or object) or a `bucket`. normalizeRoutes counts the presence of these keys and throws when a route specifies neither or both.

Source

Thrown at platform/src/components/aws/router.ts:1756

      const cdn = createCdn();
      return cdn;

      function normalizeRoutes() {
        return output(args.routes!).apply((routes) => {
          const normalizedRoutes = Object.fromEntries(
            Object.entries(routes).map(([path, route]) => {
              // Route path must start with "/"
              if (!path.startsWith("/"))
                throw new Error(
                  `In "${name}" Router, the route path "${path}" must start with a "/"`,
                );

              route = typeof route === "string" ? { url: route } : route;

              const hasUrl = "url" in route ? 1 : 0;
              const hasBucket = "bucket" in route ? 1 : 0;
              if (hasUrl + hasBucket !== 1)
                throw new Error(
                  `In "${name}" Router, the route path "${path}" can only have one of url or bucket`,
                );

              return [path, route];
            }),
          );

          normalizedRoutes["/*"] = normalizedRoutes["/*"] ?? {
            url: "https://do-not-exist.sst.dev",
          };

          return normalizedRoutes;
        });
      }

      function createCfRequestDefaultFunction() {
        defaultCfFunction =
          defaultCfFunction ??

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Provide exactly one of `url` or `bucket` on each route entry
  2. Remove the extra key (e.g. delete the spread `...common` that injects a second target)
  3. If neither is intended, drop the route entirely or fix the key name

Example fix

// before
routes: { "/api/*": { url: "https://api.example.com", bucket: myBucket } }
// after
routes: { "/api/*": { url: "https://api.example.com" } }
Defensive patterns

Strategy: validation

Validate before calling

for (const [path, route] of Object.entries(routes)) {
  const r = typeof route === "string" ? { url: route } : route;
  const count = ("url" in r ? 1 : 0) + ("bucket" in r ? 1 : 0);
  if (count !== 1) throw new Error(`Route "${path}" must set exactly one of url or bucket`);
}

Type guard

function hasOneTarget(r: object): boolean {
  return ("url" in r ? 1 : 0) + ("bucket" in r ? 1 : 0) === 1;
}

Try / catch

try {
  const r = new Router(ctx, "R", { routes });
} catch (e) {
  if (String(e).includes("one of url or bucket")) console.error("Route entry ambiguous");
  throw e;
}

Prevention

When it happens

Trigger: A route entry like `{ url: "...", bucket: "..." }`, `{ url: ..., bucket: ... }` inherited via spread, or `{}` (neither key) passed to the Router's `routes` map.

Common situations: Spreading a shared options object that already contains `bucket` into a route that also sets `url`; building route configs programmatically and leaving a route empty; typos like `buckets` that leave the route with zero recognized targets.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/c9a32d8db7e9945e. Report an issue: GitHub.