Skip to content

feat(router): load-time query prefetch helper for tanstack-query sub-entry#2347

Merged
brandonroberts merged 11 commits into
alphafrom
feat/load-queries-tanstack-query
May 26, 2026
Merged

feat(router): load-time query prefetch helper for tanstack-query sub-entry#2347
brandonroberts merged 11 commits into
alphafrom
feat/load-queries-tanstack-query

Conversation

@brandonroberts

Copy link
Copy Markdown
Member

PR Checklist

Adds load-time query prefetching for @analogjs/router/tanstack-query via a new definePageLoadQueries helper plus an automatic Router-events hydrator. Surfaced two SSR-correctness bugs while integrating into apps/tanstack-query-app; both are fixed in-tree. Updates the docs page and adds an end-to-end demo with Playwright coverage.

Closes #

Affected scope

  • Primary scope: router
  • Secondary scopes: platform

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

Commit preservation note [optional]

The 10 commits intentionally cross three concerns and the bisect lanes matter:

  1. The feature itself — four commits land definePageLoadQueries + the Router-events hydrator + the SSR TransferState mirror under @analogjs/router/tanstack-query, with tests and docs.
  2. The platform fixfix(platform): safe-access event.context.params in page endpoint wrapper is unrelated to TanStack Query. It's a pre-existing bug in the page-endpoint wrapper (added in feat(platform)!: migrate to nitro/vite and split analog() into analog() + angular() + nitro() #2343's Nitro v3 / h3 v2 migration) that surfaced because no current app exercises a non-trivial load(). Squashing it into the feature loses that history; rebase-merge keeps git blame on the wrapper pointing at a focused fix.
  3. The two SSR-correctness fixesfix(router): dehydrate on first post-render app-stable and fix: isolate QueryClient per SSR request via InjectionToken factory — both fix pre-existing bugs in the SSR pipeline. Worth their own commits so a future bisect on TanStack-Query-related issues lands on the actual fix, not "the big PR".

Squash would collapse all of this into one commit and hurt blame/bisect for @analogjs/platform and the pre-existing SSR flow.

What is the new behavior?

@analogjs/router/tanstack-query/server

Adds definePageLoadQueries — a definePageLoad wrapper that constructs a per-request QueryClient and exposes it to the .server.ts load() handler:

// src/app/pages/posts.server.ts
import { definePageLoadQueries } from '@analogjs/router/tanstack-query/server';
import { queryOptions } from '@tanstack/angular-query-experimental';

export const postsQuery = queryOptions({
  queryKey: ['posts'],
  queryFn: async ({ signal }) =>
    fetch('https://api.example.com/posts', { signal }).then((r) => r.json()),
});

export const load = definePageLoadQueries({
  handler: async ({ client }) => {
    await client.prefetchQuery(postsQuery);
  },
});

The handler runs in the Nitro page-load context (per-request QueryClient, full params/query/event/fetch from definePageLoad's typed context, Standard Schema validation inherited). It returns { __analogQueries: DehydratedState, data: TData }; the component reads vanilla injectQuery(() => postsQuery) and gets a warm cache on first render.

@analogjs/router/tanstack-query

provideAnalogQuery() gains a second ENVIRONMENT_INITIALIZER that subscribes to Router.events and, on ResolveEnd, walks the activated snapshot tree and merges any data['load'].__analogQueries dehydrated payloads into the active QueryClient. Runs on both server and client; on the server it also mirrors the dehydrated state into TransferState (ANALOG_QUERY_STATE_KEY) so the existing client-side hydration picks it up on bootstrap.

@analogjs/router/tanstack-query/server

provideServerAnalogQuery() moves off BEFORE_APP_SERIALIZED and onto ApplicationRef.isStable. Angular's TRANSFER_STATE_SERIALIZATION_PROVIDERS callback runs in declaration order ahead of provideServerAnalogQuery()'s old hook — so anything we wrote to TransferState in BEFORE_APP_SERIALIZED was serialized too late to reach the client. The new path subscribes to appRef.isStable.pipe(skipWhile(s => s), filter(Boolean), take(1)) — the skipWhile steps past the initial "no pending tasks yet" BehaviorSubject emission so the dehydrate fires on the post-render stable, with all queries settled, before Angular's serializer runs. Order-independent of how the user declares providers.

@analogjs/platform

event.context?.params instead of event.context.params in the page-endpoint wrapper. event.context is undefined for sub-handler dispatches that didn't go through h3's router-params layer — e.g. the internal fetchWithEvent calls Angular SSR uses to reach .server.ts page-load endpoints — so accessing .params threw TypeError: Cannot read properties of undefined (reading 'params') before the user's load function ever ran. Mirrors the optional chaining already used for event.node?.req / event.node?.res.

Docs + demo app

  • New "Prefetching Queries in load()" section in apps/docs-app/docs/integrations/tanstack-query/index.md, plus a :::warning callout about the provideTanStackQuery SSR isolation footgun (see below).
  • The integration snippet in the docs now uses an InjectionToken<QueryClient> with factory: () => new QueryClient() instead of provideTanStackQuery(new QueryClient()) — the latter pattern shares a single QueryClient across every SSR request in the Node process, leaking query state between responses. The InjectionToken factory pattern gives each bootstrapApplication call its own client. No library change required — provideTanStackQuery already accepts an InjectionToken<QueryClient> upstream.
  • New /tanstack-query-load demo page in apps/tanstack-query-app showing the helper end-to-end, plus a Playwright e2e (tanstack-query-load.spec.ts) asserting (a) the list renders from the prefetch, (b) the client issues no /api/v1/query-posts request, (c) scope isolation across requests.
  • app.config.ts swapped the hand-rolled ENVIRONMENT_INITIALIZER for provideAnalogQuery() (which is what it was inlining) and adopted the InjectionToken factory.

Test plan

  • nx format:check (lint passes on router and platform)
  • nx build router / nx build platform / nx build tanstack-query-app
  • nx test router — 320/320 (was 318; +7 new tests across the two specs in tanstack-query/)
  • nx test platform — 363/363 (incl. new page-endpoints-plugin coverage)
  • Manual SSR verification across four pages — basic, multi, infinite-style, load — confirms each request's ng-state contains only its own queries and the load page renders with fetch count = 1 (load-time prefetch only; no client request)
  • nx e2e tanstack-query-app-e2e — added but not yet run locally (CI will pick up)

Does this PR introduce a breaking change?

  • Yes
  • No

All additions to @analogjs/router/tanstack-query. provideServerAnalogQuery()'s implementation changed (moved from BEFORE_APP_SERIALIZED to an ApplicationRef.isStable subscription) but its export name, signature, and contract are unchanged — and the new path strictly fixes a case that didn't work before (component-issued queries weren't reaching the client at all).

Other information

The provideTanStackQuery(new QueryClient()) SSR isolation issue is the kind of footgun that's hard to spot locally (single-process dev server, refresh hides it) and easy to ship to production. The docs change with the warning callout should keep future users out of it without requiring a library change — provideTanStackQuery already supports the safe pattern upstream.

The route-tree codegen (apps/tanstack-query-app/src/routeTree.gen.ts) is not updated to include the new /tanstack-query-load route because experimental.typedRouter is off in that app's vite config. The route works at runtime; only typed-route helpers would miss it. Enabling the flag is a separate decision.

🤖 Generated with Claude Code

brandonroberts and others added 10 commits May 26, 2026 10:03
Adds `definePageLoadQueries` under `@analogjs/router/tanstack-query/server`,
a thin wrapper around `definePageLoad` that constructs a per-request
`QueryClient` and exposes it to the handler. The handler runs in the
Nitro `.server.ts` context — call `client.prefetchQuery` /
`ensureQueryData` to warm the cache, return optional supplemental data.

The wrapper returns `{ __analogQueries: DehydratedState, data: TData }`;
the dehydrated payload is what the router hydrator (added separately)
merges into the active client on `ResolveEnd`. Per-request isolation:
a fresh `QueryClient` is built every invocation.

Inherits `params` / `query` Standard Schema validation from
`definePageLoad`; validation failures short-circuit with `fail(422)`
before any prefetch runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a second `ENVIRONMENT_INITIALIZER` to `provideAnalogQuery()` that
subscribes to `Router.events` and, on every `ResolveEnd`, walks the
activated snapshot tree and merges any `data['load'].__analogQueries`
dehydrated payloads into the active `QueryClient`.

Runs on both server and client:

- **Server**: after `load()` returns, the router emits `ResolveEnd`,
  the hydrator merges the dehydrated cache, then components render
  against the warm client; `BEFORE_APP_SERIALIZED` captures the
  merged state into `TransferState` as before.
- **Client**: subsequent navigations re-fetch `/_analog/pages/...`
  for the new route; the same merge runs. Initial paint is still
  covered by the existing TransferState hydration.

`hydrate` is idempotent — re-merging the same payload is a no-op.
`Router` is injected optionally so apps that don't use `@angular/router`
still bootstrap. Subscription is torn down via `DestroyRef.onDestroy`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the two new code paths added under `@analogjs/router/tanstack-query`:

`define-page-load-queries.spec.ts` (new):
- result shape — `{ __analogQueries, data }` with the dehydrated cache
- fresh `QueryClient` per invocation
- user-supplied `client` factory honored (so default options flow)
- params validation failure returns a 422 `Response` before the handler runs

`provide-analog-query.spec.ts` (extended):
- `ResolveEnd` with `data['load'][__analogQueries]` hydrates the active client
- snapshot walk merges dehydrated state from nested child routes
- load data without an `__analogQueries` field is a no-op

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'Prefetching Queries in load()' section to the TanStack Query
integration docs, showing the .server.ts handler shape, the matching
page component, params validation pass-through, and the optional client
factory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the hand-rolled `ENVIRONMENT_INITIALIZER` that was inlining the
exact code `provideAnalogQuery()` ships, and calls the helper directly.
Behavior on the existing TransferState path is identical; the helper
additionally subscribes to `Router.events` so the load-time prefetch
helper added in @analogjs/router can merge route-data payloads on
`ResolveEnd`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`event.context` is undefined for sub-handler dispatches that didn't go
through h3's router-params layer — e.g. the internal `fetchWithEvent`
calls Angular SSR uses to reach `.server.ts` page-load endpoints.
Accessing `event.context.params` then threw `TypeError: Cannot read
properties of undefined (reading 'params')` before the user's `load`
function ever ran, surfacing as a 500 on every page that defined a
load handler. Mirrors the optional chaining already used for
`event.node?.req` / `event.node?.res`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The route-events hydrator added in 9d6bdaa merges dehydrated load
payloads into the active `QueryClient` during navigation, but on the
server that wasn't enough to feed the client: Angular's built-in
`TRANSFER_STATE_SERIALIZATION_PROVIDERS` callback runs as part of
`BEFORE_APP_SERIALIZED`, ahead of `provideServerAnalogQuery()`'s
`dehydrate(client)` hook, so anything the dehydrate path wrote was
serialized too late to land in the `ng-state` script.

The hydrator runs during `ResolveEnd` — *before* Angular's serializer
fires — so it's the right place to write. When `PLATFORM_ID` resolves
to the server, the hydrator now also copies each load route's
dehydrated state into the same `TransferState` key
(`ANALOG_QUERY_STATE_KEY`) the existing client-side initializer reads
on bootstrap. Multiple parent/child route payloads are merged by
`queryHash` so duplicates don't accumulate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new `/tanstack-query-load` demo page that prefetches the
existing `/api/v1/query-posts` server route inside its `.server.ts`
`load()` handler via `definePageLoadQueries`. The component reads
`postsQuery` through `serverQueryOptions` with the same queryKey, so
on first render the cache is already warm — fetch count stays at 1
across page reloads and the client issues no `/api/v1/query-posts`
request.

Includes a home-page card and a Playwright e2e covering the
no-client-request and scope-isolation behaviors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…P_SERIALIZED

Component-issued queries weren't reaching the client because Angular's
`TRANSFER_STATE_SERIALIZATION_PROVIDERS` registers its own
`BEFORE_APP_SERIALIZED` callback in `provideServerRendering()` —
typically declared *before* `provideServerAnalogQuery()` — and runs in
declaration order. Angular's serializer read `TransferState.toJson()`
and emitted the `ng-state` script *before* our dehydrate hook ever
fired, so anything we wrote in `BEFORE_APP_SERIALIZED` was serialized
too late to reach the client.

Move the dehydrate to an `ENVIRONMENT_INITIALIZER` that subscribes to
`ApplicationRef.isStable`, with `skipWhile(stable => stable)` to step
past the initial 'no pending tasks yet' value the underlying
`BehaviorSubject` emits on subscribe. The first `true` after rendering
kicks off (queries fire, become unstable, settle) is the post-render
stable, and that's where we dehydrate.

This fires before `renderApplication`'s own `await whenStable()`
resumes — both subscriptions are on the same observable and ours is
registered first via the environment initializer — so the
`TransferState` write happens before `BEFORE_APP_SERIALIZED` runs and
Angular's serializer picks up our key. Order-independent of how the
user declares providers.

Removes the brittle `BEFORE_APP_SERIALIZED` registration entirely; the
unit test now drives a mocked `isStable` subject through
`true → false → true` and asserts the dehydrate only fires on the
post-render transition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`provideTanStackQuery(new QueryClient())` evaluates the constructor
once at module-load time, so every SSR request on the same Node
process shares the same `QueryClient` instance — query state from one
response leaks into the next request's dehydrated `TransferState`.

Pass an `InjectionToken<QueryClient>` with `factory: () => new
QueryClient()` instead. `bootstrapApplication` creates a fresh root
injector per SSR call, so the factory runs once per request and each
response ships only its own queries. On the browser the same provider
still resolves to a single instance.

Updates the tanstack-query-app config and the docs page (with a
prominent warning callout), since the bad pattern came from the
docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented May 26, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit b4748a6
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/6a15d45dd586dc0008adee6b
😎 Deploy Preview https://deploy-preview-2347--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented May 26, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit b4748a6
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6a15d45dcb2edf0008f116c0
😎 Deploy Preview https://deploy-preview-2347--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 457dd8ba-a6fe-4323-ab17-bff583185474

📥 Commits

Reviewing files that changed from the base of the PR and between c98954a and b4748a6.

📒 Files selected for processing (2)
  • packages/router/tanstack-query/src/provide-analog-query.spec.ts
  • packages/router/tanstack-query/src/provide-analog-query.ts

📝 Walkthrough

Walkthrough

Adds server-side definePageLoadQueries to prefetch TanStack Query queries with a per-request QueryClient and return dehydrated cache under ANALOG_QUERIES_KEY. provideAnalogQuery() now hydrates QueryClient from route load payloads on ResolveEnd and mirrors merged payloads into TransferState on the server. provideServerAnalogQuery() uses an ENVIRONMENT_INITIALIZER to serialize after post-render stability. App bootstrap switched to a QUERY_CLIENT InjectionToken. Nitro page-endpoints generation was hardened to use optional chaining for event.context params. Demo page, RFC, unit tests, and E2E tests were added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commit format with supported 'router' scope and clearly summarizes the main change: adding a load-time query prefetch helper for the tanstack-query sub-entry.
Description check ✅ Passed The description thoroughly covers the changeset: explains definePageLoadQueries helper, Router-events hydrator, SSR fixes, docs updates, demo app integration, and test coverage—all directly relevant to the implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added scope:docs Documentation changes scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router labels May 26, 2026
@github-actions

Copy link
Copy Markdown

This PR touches multiple package scopes: platform, router.

Please confirm the changes are closely related. Squash merge is highly preferred. If you recommend a non-squash merge, add a brief note explaining why the commit boundaries matter and why this PR should bypass focused changes per package.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/tanstack-query-app-e2e/tests/tanstack-query-load.spec.ts (1)

26-32: ⚡ Quick win

Consider asserting data content differs between scopes.

The test validates that each scope triggers one fetch, but doesn't verify that the rendered data actually differs between load-scope-a and load-scope-b. If a cache-key collision bug caused both scopes to share state, the test would still pass as long as each performed one fetch. To strengthen validation of the per-request QueryClient isolation guarantee, consider adding assertions that compare the actual rendered content (e.g., post titles or IDs) between the two navigations.

🧪 Example enhanced isolation assertion
 test('isolates scope state across requests', async ({ page }) => {
   await page.goto('/tanstack-query-load?scope=load-scope-a');
   await expect(page.locator('`#load-fetch-count`')).toContainText('1');
+  const scopeAContent = await page.locator('`#load-scope`').textContent();

   await page.goto('/tanstack-query-load?scope=load-scope-b');
   await expect(page.locator('`#load-fetch-count`')).toContainText('1');
+  const scopeBContent = await page.locator('`#load-scope`').textContent();
+  expect(scopeAContent).not.toBe(scopeBContent);
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/tanstack-query-app-e2e/tests/tanstack-query-load.spec.ts` around lines
26 - 32, The test "isolates scope state across requests" currently only asserts
fetch counts; capture the rendered data after each navigation (e.g., text from
the page locator that shows the loaded item such as '`#load-data`' or
'`#post-title`') and assert the two values differ to ensure per-scope isolation;
update the steps after page.goto('/tanstack-query-load?scope=load-scope-a') and
page.goto('/tanstack-query-load?scope=load-scope-b') to read and store the
content from the appropriate data locator (in addition to '`#load-fetch-count`')
and add an assertion that the stored values are not equal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/tanstack-query-app/src/app/pages/`(home).page.ts:
- Around line 134-147: The homepage copy still shows "Examples 4" and only lists
four patterns even though a new demo card ("Prefetch in load()" represented by
the anchor/card with badge "Load-time prefetch" and title "Prefetch in load()")
was added; update the summary/count text and the examples list to include this
new demo (increment the displayed examples count to 5 and add an entry for the
load-time prefetch/demo) so the summary and list match the added card.

In `@packages/router/tanstack-query/RFC-LOAD-QUERIES.md`:
- Around line 3-5: Update the RFC metadata "Status:" line in RFC-LOAD-QUERIES.md
to reflect that work has been implemented and validated rather than "Draft —
implementation not yet started"; locate the "Status:" metadata string in the
file and change it to an accurate state such as "Implemented — implementation
and validation complete" or "In review — implementation and validation complete"
so the branch status no longer conflicts with included code.

In `@packages/router/tanstack-query/src/provide-analog-query.ts`:
- Around line 106-116: mergeDehydrated currently keeps the first query for a
given queryHash (base wins) which can cause stale data; change it to
last-writer-wins by merging queries so that when queryHash duplicates exist the
entry from next overrides base. Locate mergeDehydrated and adjust the queries
merge logic (using DehydratedState, queries, and queryHash) to produce a
map/ordered list where entries from next replace base entries with the same
queryHash, preserve order predictably, keep mutations concatenation as-is, and
add/extend a unit test asserting that when the same queryHash exists in base and
next the next entry is the one kept.

---

Nitpick comments:
In `@apps/tanstack-query-app-e2e/tests/tanstack-query-load.spec.ts`:
- Around line 26-32: The test "isolates scope state across requests" currently
only asserts fetch counts; capture the rendered data after each navigation
(e.g., text from the page locator that shows the loaded item such as
'`#load-data`' or '`#post-title`') and assert the two values differ to ensure
per-scope isolation; update the steps after
page.goto('/tanstack-query-load?scope=load-scope-a') and
page.goto('/tanstack-query-load?scope=load-scope-b') to read and store the
content from the appropriate data locator (in addition to '`#load-fetch-count`')
and add an assertion that the stored values are not equal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 127707ce-0637-4007-917f-0e5a90799d21

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4d1ba and c98954a.

📒 Files selected for processing (16)
  • apps/docs-app/docs/integrations/tanstack-query/index.md
  • apps/tanstack-query-app-e2e/tests/tanstack-query-load.spec.ts
  • apps/tanstack-query-app/src/app/app.config.ts
  • apps/tanstack-query-app/src/app/pages/(home).page.ts
  • apps/tanstack-query-app/src/app/pages/tanstack-query-load.page.ts
  • apps/tanstack-query-app/src/app/pages/tanstack-query-load.server.ts
  • packages/platform/src/lib/nitro/page-endpoints-plugin.spec.ts
  • packages/platform/src/lib/nitro/page-endpoints-plugin.ts
  • packages/router/tanstack-query/RFC-LOAD-QUERIES.md
  • packages/router/tanstack-query/server/src/define-page-load-queries.spec.ts
  • packages/router/tanstack-query/server/src/define-page-load-queries.ts
  • packages/router/tanstack-query/server/src/index.ts
  • packages/router/tanstack-query/src/constants.ts
  • packages/router/tanstack-query/src/provide-analog-query.spec.ts
  • packages/router/tanstack-query/src/provide-analog-query.ts
  • packages/router/tanstack-query/src/provide-server-analog-query.ts

Comment on lines +134 to +147
<a
routerLink="/tanstack-query-load"
class="card card-border bg-base-100 shadow-md transition-all hover:-translate-y-1 hover:shadow-xl"
>
<div class="card-body">
<div class="badge badge-info badge-outline">Load-time prefetch</div>
<h2 class="card-title text-xl">Prefetch in <code>load()</code></h2>
<p class="text-base-content/70">
Declare a route's queries in its <code>.server.ts</code> handler
with <code>definePageLoadQueries</code> — components render
against a warm cache, no SSR-to-client refetch.
</p>
</div>
</a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update homepage summary stats to reflect the new demo card.

Adding this card makes the set effectively 5 demos, but the summary still says Examples 4 and lists only four patterns (Line 56 and Line 57), so homepage copy is now inconsistent.

Suggested copy update
-              <div class="stat-value">4</div>
-              <div class="stat-desc">Basic, multi, infinite, optimistic</div>
+              <div class="stat-value">5</div>
+              <div class="stat-desc">Basic, multi, infinite, optimistic, load</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/tanstack-query-app/src/app/pages/`(home).page.ts around lines 134 - 147,
The homepage copy still shows "Examples 4" and only lists four patterns even
though a new demo card ("Prefetch in load()" represented by the anchor/card with
badge "Load-time prefetch" and title "Prefetch in load()") was added; update the
summary/count text and the examples list to include this new demo (increment the
displayed examples count to 5 and add an entry for the load-time prefetch/demo)
so the summary and list match the added card.

Comment on lines +3 to +5
**Branch:** `feat/load-queries-tanstack-query` (off `alpha`)
**Status:** Draft — implementation not yet started, awaiting approval
**Scope:** Sub-entry-point only. No new package, no new top-level entry.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

RFC status metadata is stale for this PR.

Line 4 says implementation has not started, but this branch already includes implementation and validation work; updating status wording will avoid confusing rollout state.

Suggested metadata tweak
-**Status:** Draft — implementation not yet started, awaiting approval
+**Status:** Draft — implementation prototype exists in PR `#2347` (pending merge/approval)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Branch:** `feat/load-queries-tanstack-query` (off `alpha`)
**Status:** Draft — implementation not yet started, awaiting approval
**Scope:** Sub-entry-point only. No new package, no new top-level entry.
**Branch:** `feat/load-queries-tanstack-query` (off `alpha`)
**Status:** Draft — implementation prototype exists in PR `#2347` (pending merge/approval)
**Scope:** Sub-entry-point only. No new package, no new top-level entry.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router/tanstack-query/RFC-LOAD-QUERIES.md` around lines 3 - 5,
Update the RFC metadata "Status:" line in RFC-LOAD-QUERIES.md to reflect that
work has been implemented and validated rather than "Draft — implementation not
yet started"; locate the "Status:" metadata string in the file and change it to
an accurate state such as "Implemented — implementation and validation complete"
or "In review — implementation and validation complete" so the branch status no
longer conflicts with included code.

Comment thread packages/router/tanstack-query/src/provide-analog-query.ts Outdated
… merge

`mergeDehydrated()` was keeping the first `queryHash` from `base` and
dropping later duplicates in `next` — so if a parent and child route
both prefetched the same key, the parent's (older) dehydrated entry
was serialized into `TransferState` and the client could hydrate
stale data on first paint. Switch to a `Map`-based merge keyed by
`queryHash` so `next` overrides `base` for duplicate hashes, matching
`hydrate()`'s own newer-wins semantics for the live QueryClient.

Adds a focused spec that drives a parent → child route tree with
overlapping query keys and asserts the TransferState entry reflects
the child (later) write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@brandonroberts brandonroberts merged commit 125807e into alpha May 26, 2026
17 of 23 checks passed
@brandonroberts brandonroberts deleted the feat/load-queries-tanstack-query branch May 26, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:docs Documentation changes scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant