Skip to content

[🐞] beta.40: ancestor layout's routeLoader$ gets empty params + truncated request.url when resolving a route nested under it #8944

Description

@ixcans

Which component is this bug report for?

Qwik Router

Describe the bug

On @qwik.dev/router@2.0.0-beta.40, when a routeLoader$ is declared in a layout.tsx that lives at a dynamic [param] segment, and the actual request being served is for a route nested one level deeper than that layout, the layout's own loader receives:

  • params — an empty object (missing the param the child route resolves correctly)
  • url.pathname — truncated to the layout's own segment (e.g. /x/ instead of /x/sub/)
  • request.url — also truncated the same way (not just a derived value — the raw Request itself)

...while the child route's own routeLoader$, running in the same request, sees the correct full params/url/request.url.

This is absent on 2.0.0-beta.35 (confirmed via bisection — identical repro, only the Qwik version pin changed). It is not adapter-specific — I reproduced it both on a custom Bun-based SSR adapter and on Qwik Router's own official node-server adapter running under plain Node.js, byte-for-byte identically. It does not reproduce under vite dev (live/unbundled SSR) — only in the built/production SSR bundle path both adapters share. It is also not limited to a literal first/"cold" request — it reproduces deterministically on repeated requests to an already-warm server process too; the only thing that avoids it is a client-side <Link>/useNavigate() transition, which never goes through this server-side document-request path at all.

Suspected regression source: beta.40's sole substantial change, the Vite 8 + Rolldown migration (#8909) — bisection confirms the bug is new in beta.40 relative to beta.35, and no other change of similar scope shipped in between.

Real-world impact: found while investigating a production app (a per-tenant dashboard) where this silently redirected users away from any deep-linked, bookmarked, or refreshed page — the entire nested-route tree was affected. The app reverted from beta.40 back to beta.35 because of this.

Related but distinct (same general subsystem/symptom family, different trigger — not duplicates): #8859 (a client-side SPA-action re-fetch missing a route-path header, landing a loader at / — different mechanism, pre-dates beta.40) and #8856 (merged in beta.39, touches getLoaderRequestEvent()'s per-loader RequestEvent field overriding — same code area, but scoped to a different, client-triggered path, and predates the Vite8/Rolldown migration so it can't be this regression).

Reproduction

https://github.com/ixcans/qwik-router-layout-params-repro

Scaffolded from the official Qwik CLI (npm create qwik@beta empty app) with zero added business logic — three small route files, 58 lines total:

// src/routes/[slug]/layout.tsx
import { Slot, component$ } from "@qwik.dev/core";
import { routeLoader$ } from "@qwik.dev/router";

export const useSlugParam = routeLoader$((ev) => {
  console.error(
    "[LAYOUT useSlugParam] params=%s pathname=%s request.url=%s",
    JSON.stringify(ev.params), ev.url.pathname, ev.request.url,
  );
  return ev.params.slug ?? null;
});

export default component$(() => <Slot />);
// src/routes/[slug]/sub/index.tsx
import { component$ } from "@qwik.dev/core";
import { routeLoader$, useLocation } from "@qwik.dev/router";
import { useSlugParam } from "../layout";

export const useSubParam = routeLoader$((ev) => {
  console.error(
    "[CHILD  useSubParam] params=%s pathname=%s request.url=%s",
    JSON.stringify(ev.params), ev.url.pathname, ev.request.url,
  );
  return ev.params.slug ?? null;
});

export default component$(() => {
  const layoutSlug = useSlugParam();
  const childSlug = useSubParam();
  const loc = useLocation();
  return (
    <div>
      <div>layoutSlug: {JSON.stringify(layoutSlug.value)}</div>
      <div>childSlug: {JSON.stringify(childSlug.value)}</div>
      <div>useLocation().params: {JSON.stringify(loc.params)}</div>
    </div>
  );
});
npm install
npm run build
npm run build.preview
npm run preview -- --open=false

Then make a fresh request (curl, or a browser hard-navigation — not a client-side link click) to /x/sub/ (any literal value in place of x).

Steps to reproduce

  1. Clone https://github.com/ixcans/qwik-router-layout-params-repro
  2. npm install && npm run build && npm run build.preview && npm run preview -- --open=false
  3. curl http://localhost:<printed-port>/x/sub/
  4. Check the server's stderr for the two log lines

Expected behavior

Both loaders should observe the same, correct params/url.pathname/request.url for the request actually being served.

Actual behavior

[LAYOUT useSlugParam] params={} pathname=/x/ request.url=http://localhost:PORT/x/
[CHILD  useSubParam] params={"slug":"x"} pathname=/x/sub/ request.url=http://localhost:PORT/x/sub/

Rendered page: layoutSlug: null, childSlug: "x". Requesting the bare /x/ (no nested sub) does not reproduce this — the layout loader sees correct params there. The corruption is specific to an ancestor layout resolving in the same request as a route nested deeper under it.

Confirmed absent on 2.0.0-beta.35 with the identical repro (only the version pin changed).

System Info

@qwik.dev/core: 2.0.0-beta.40
@qwik.dev/router: 2.0.0-beta.40
vite: 8.0.16
Tested on both a custom Bun-based SSR adapter and Qwik Router's official node-server adapter (Node.js), same result on both.

Additional information

No existing issue found covering this exact bug after a thorough search (issues, PRs, discussions, and every issue filed since beta.40 shipped) — happy to help narrow this down further if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    V2bugSomething isn't workingrouter

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions