Skip to content

async: move poll to usePoll - #8904

Merged
maiieul merged 13 commits into
mainfrom
utils-lib
Aug 17, 2026
Merged

async: move poll to usePoll#8904
maiieul merged 13 commits into
mainfrom
utils-lib

Conversation

@wmertens

@wmertens wmertens commented Aug 2, 2026

Copy link
Copy Markdown
Member

The current ComputedSignal API with poll and expires takes quite a lot of core code while it's fairly rare/doesn't cover all use cases.

Furthermore, there was a disconnect between the expires option in routeLoaders and the cache-control headers that might have been set by middleware.

This PR does a few things:

  • adds a new @qwik.dev/utils package, which contains non-core code that can still access internals and which is tied to the exact core version
  • adds a usePoll hook which can be used to poll a computed signal once it hits the client
  • adds .clear() to computed signals, which clears their value, which will cause Suspense boundaries to show while recalculating
  • adds a cacheControl option to routeLoaders. Set to "immutable" to make it be generated during SSG.
  • refetches all routeLoaders on navigation (except immutable ones), thereby fully relying on the browser cache and cache-control settings
  • breaking removes poll, expires, and allowStale from ComputedSignal and thus also routeLoaders
  • limits link-hover data prefetch to once per navigation
  • moves zod$ into utils

@changeset-bot

changeset-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b4a52f6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@qwik.dev/core Major
@qwik.dev/router Major
@qwik.dev/utils Major
eslint-plugin-qwik Major
@qwik.dev/react Major
create-qwik Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

wmertens and others added 9 commits August 2, 2026 14:18
Polling moves out of the async signal engine: usePoll(signal, expires)
invalidates the signal on an interval that starts on document idle and
resets whenever a new result lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Polling now lives in usePoll (@qwik.dev/utils). Drops the expires/poll/
interval options and accessors, the NO_POLL flag, the expires slot in the
AsyncSignal wire format, and the SSR eager-resume hack for polling signals
(the usePoll visible task resumes on qidle instead).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
allowStale:false made every invalidation clear the value; clear() makes
that a one-shot, caller-controlled operation and drops the
CLEAR_ON_INVALIDATE flag and the loader allowStale plumbing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The browser HTTP cache becomes the single freshness authority for loader
data. cacheControl accepts any cacheControl() value or a function of the
request event (null return skips the header); default is no-cache. SSG
writes per-loader files only for cacheControl: 'immutable' loaders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erender on unchanged data

Every navigation invalidates every loader signal; the browser HTTP cache
(driven by each loader's cacheControl) is the freshness authority. The
TTL fetch cache becomes a per-navigation dedupe map shared between hover
prefetch and the nav's own fetches. Loader responses keep their raw text
so an unchanged payload skips deserialization and returns the previous
object, firing no effects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…utils

Also refresh the core-notes agent guidance and add the utils package to
the monorepo map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cacheControl:'immutable' data cannot change until a rebuild, so the
per-navigation invalidation sweep skips those signals; their tracked URL
inputs still invalidate them when the path or search changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skip immutable loaders in the eager nav kick-off and in link data
prefetch: if the SSR'd page never reads them client-side, their JSON is
never downloaded. First read fetches once per deploy (immutable
browser caching covers every fetch after that).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Aug 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@qwik.dev/core

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@8904

@qwik.dev/router

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@8904

eslint-plugin-qwik

npm i https://pkg.pr.new/QwikDev/qwik/eslint-plugin-qwik@8904

create-qwik

npm i https://pkg.pr.new/QwikDev/qwik/create-qwik@8904

@qwik.dev/optimizer

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/optimizer@8904

@qwik.dev/devtools

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/devtools@8904

commit: b4a52f6

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
qwik-docs ✅ Ready (View Log) Visit Preview b4a52f6

Comment thread packages/qwik-utils/src/use-poll.ts
GitHub strips embedded svg
@wmertens

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

# Conflicts:
#	packages/qwik-router/src/runtime/src/link-prefetch.ts
#	packages/qwik-router/src/runtime/src/router-outlet-component.tsx

Co-authored-by: wmertens <54934+wmertens@users.noreply.github.com>

Copilot AI commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in f3d5ea2. The two conflicts were in link-prefetch.ts and router-outlet-component.tsx — both adopted the origin/main approach where linkPrefetchInit reads manifestHash directly from the DOM element rather than passing it as a parameter.

@wmertens
wmertens marked this pull request as ready for review August 17, 2026 10:33
@wmertens
wmertens requested review from a team as code owners August 17, 2026 10:33
Copilot AI lite review requested due to automatic review settings August 17, 2026 10:33
@maiieul maiieul moved this from In progress to Waiting For Review in Qwik Development Aug 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors “polling/expiry” behavior out of @qwik.dev/core computed signals into a new @qwik.dev/utils package, and reworks Qwik Router loader freshness to rely on browser HTTP caching (Cache-Control/ETag) with a new cacheControl loader option (including an 'immutable' SSG-friendly mode).

Changes:

  • Introduces @qwik.dev/utils (tied to core) and adds usePoll(signal, ms) for client-only polling on document idle.
  • Updates computed signals: removes expires/poll/interval (+ allowStale) and adds ComputedSignal.clear() to explicitly drop the value and recompute.
  • Updates router loaders: adds cacheControl, refetches loaders on every SPA navigation (skipping immutable ones), and avoids rerenders when loader response text is unchanged.

Reviewed changes

Copilot reviewed 65 out of 68 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.json Includes packages/qwik-utils/src in TS project paths.
scripts/util.ts Adds --qwikutils boolean CLI option.
scripts/qwik-utils.ts Adds build script for the new qwik-utils package.
scripts/ci-restore-artifacts.ts Restores CI artifacts for packages/qwik-utils/lib.
scripts/build.ts Wires buildQwikUtils() into the main build pipeline.
README.md Replaces inline sponsor SVG with a referenced asset file.
pnpm-lock.yaml Adds lockfile entries for packages/qwik-utils.
packages/qwik/src/core/tests/use-computed.spec.tsx Removes SSR polling resume test now that core polling is removed.
packages/qwik/src/core/ssr/ssr-render-jsx.ts Removes SSR eager-resume tracking for polling computeds.
packages/qwik/src/core/shared/serdes/serialize.ts Removes async-signal expires serialization field.
packages/qwik/src/core/shared/serdes/serdes.unit.ts Updates serdes tests/snapshots after removing expires.
packages/qwik/src/core/shared/serdes/inflate.ts Adjusts async-signal inflation tuple layout (no expires).
packages/qwik/src/core/reactive-primitives/utils.ts Removes internal _isSignalNotInvalid helper.
packages/qwik/src/core/reactive-primitives/types.ts Removes expires/poll/interval/allowStale from ComputedOptions; removes related flags.
packages/qwik/src/core/reactive-primitives/signal.public.ts Adds ComputedSignal.clear(); removes expires/poll/interval properties.
packages/qwik/src/core/reactive-primitives/impl/signal.unit.tsx Updates type tests (removes interval).
packages/qwik/src/core/reactive-primitives/impl/computed-signal-impl.ts Removes polling/expiry engine; adds clear() behavior; updates messaging.
packages/qwik/src/core/reactive-primitives/impl/async-signal.unit.tsx Removes polling/allowStale tests; adds clear() behavior test coverage.
packages/qwik/src/core/reactive-primitives/impl/async-signal-impl.ts Updates docs comment to remove polling mention.
packages/qwik/src/core/qwik.core.api.md Updates public API docs for removed fields + new clear().
packages/qwik/src/core/internal.ts Stops exporting removed _isSignalNotInvalid.
packages/qwik-utils/vite.config.ts Adds Vite lib build config for @qwik.dev/utils.
packages/qwik-utils/tsconfig.json Adds tsconfig extending repo defaults, sets jsxImportSource.
packages/qwik-utils/src/use-poll.unit.tsx Adds unit tests for usePoll in SSR + DOM render modes.
packages/qwik-utils/src/use-poll.ts Implements usePoll() via useVisibleTask$ + interval invalidation.
packages/qwik-utils/src/index.qwik.ts Exports usePoll from the package entrypoint.
packages/qwik-utils/package.json Adds new published package manifest for @qwik.dev/utils.
packages/qwik-router/src/ssg/worker-thread.ts Switches SSG per-loader file writing to cacheControl: 'immutable'.
packages/qwik-router/src/runtime/src/use-endpoint.unit.ts Updates loader-fetch expectations and adds cache-clear refetch test.
packages/qwik-router/src/runtime/src/types.ts Replaces expires/poll/allowStale loader options with cacheControl.
packages/qwik-router/src/runtime/src/route-loaders.unit.ts Updates loader option tests; adds immutable registration test.
packages/qwik-router/src/runtime/src/route-loaders.ts Refactors loader fetch caching to per-navigation dedupe + raw-text identity optimization; adds immutable tracking.
packages/qwik-router/src/runtime/src/qwik-router.runtime.api.md Updates runtime API docs imports (adds CacheControl).
packages/qwik-router/src/runtime/src/qwik-router-component.tsx Invalidates loaders on every navigation (skipping immutable); clears per-nav fetch cache after kickoff.
packages/qwik-router/src/runtime/src/prefetch-route.unit.ts Updates prefetch tests to new immutable behavior and raw fetch result shape.
packages/qwik-router/src/runtime/src/prefetch-route.ts Prefetches loader data without relying on in-memory freshness; skips immutable loaders.
packages/qwik-router/src/runtime/src/link-prefetch.unit.ts Updates link prefetch tests for new prefetchRoute signature.
packages/qwik-router/src/runtime/src/link-prefetch.ts Removes loaderState param plumbed into link prefetching.
packages/qwik-router/src/runtime/src/link-component.unit.tsx Updates Link prefetch tests for new prefetchRoute signature.
packages/qwik-router/src/runtime/src/link-component.tsx Removes RouteStateContext dependency for prefetch data path.
packages/qwik-router/src/middleware/request-handler/resolve-request-handlers-core.ts Updates comment for SSG loader internals tracking (__cacheControl).
packages/qwik-router/src/middleware/request-handler/handlers/loader-request-event.unit.ts Updates loader test fixtures after removing __expires/__poll/__allowStale.
packages/qwik-router/src/middleware/request-handler/handlers/loader-handler.unit.ts Adds coverage for new cacheControl behavior and precedence rules.
packages/qwik-router/src/middleware/request-handler/handlers/loader-handler.ts Implements cacheControl option resolution and header precedence rules.
packages/docs/src/routes/docs/upgrade/index.mdx Updates upgrade docs to point polling to usePoll() in utils.
packages/docs/src/routes/docs/integrations/prisma/index.mdx Moves zod$ import from router to utils.
packages/docs/src/routes/docs/integrations/drizzle/index.mdx Moves zod$ import from router to utils.
packages/docs/src/routes/docs/(qwikrouter)/route-loader/index.mdx Updates loader docs for cacheControl, nav refetch behavior, and usePoll.
packages/docs/src/routes/docs/(qwikrouter)/guides/qwik-nutshell/index.mdx Moves zod$ import from router to utils.
packages/docs/src/routes/docs/(qwikrouter)/action/index.mdx Moves zod$ import from router to utils across examples.
packages/docs/src/routes/docs/(qwik)/core/state/index.mdx Updates computed-signal docs for usePoll and clear(); removes expires/poll.
packages/docs/src/routes/api/qwik/index.mdx Adds ComputedSignal.clear() API docs; removes old computed options/props docs.
packages/docs/src/routes/api/qwik/api.json Regenerates Qwik API JSON for clear() and removed fields.
packages/docs/src/routes/api/qwik-router/index.mdx Updates Router API docs for loader options text.
packages/docs/src/routes/api/qwik-router/api.json Regenerates Router API JSON for loader options text.
package.json Adds --qwikutils to build.full.
CLAUDE.md Updates repo map to include @qwik.dev/utils.
AGENTS.md Updates repo map to include @qwik.dev/utils.
.ruler/skills/qwik-core-development/references/core-notes.md Updates core guidance to reflect removal of polling/expiry and new usePoll/clear().
.ruler/AGENTS.md Updates repo map to include @qwik.dev/utils.
.github/workflows/ci.yml Adds build/cache/artifact wiring for qwik-utils.
.github/assets/kunaico.svg Adds sponsor SVG asset referenced by README.
.changeset/utils-package-usepoll.md Changeset for new utils package and usePoll.
.changeset/router-nav-invalidation.md Changeset for nav refetch + no-rerender-on-unchanged behavior.
.changeset/router-loader-cache-control.md Changeset for breaking loader caching option migration to cacheControl.
.changeset/core-remove-expires-poll.md Changeset for breaking removal of computed polling fields.
.changeset/core-clear-signal.md Changeset for breaking allowStale removal and new clear().
.changeset/config.json Adds @qwik.dev/utils to the fixed version group.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (1)

packages/qwik-router/src/runtime/src/route-loaders.ts:276

  • Same as above: cap navFetchCache on the non-abortable path as well so it can’t grow without bound within a single navigation cycle.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/qwik-utils/src/use-poll.ts Outdated
Comment thread packages/qwik-router/src/runtime/src/route-loaders.ts
Comment thread packages/qwik-router/src/runtime/src/route-loaders.ts
@wmertens

Copy link
Copy Markdown
Member Author

@copilot fix the unit and e2e test failures

claude and others added 2 commits August 17, 2026 11:30
The nav-wide loader invalidation iterated every key in the loader state,
including the `__qwik_route_loader_value__*` resume entries, which are not
signals. Every SPA navigation threw, breaking client-side routing.

Also update the link prefetch expectation for the dropped loaderState
argument, refresh the SSG state snapshot, and make usePoll generic with a
5ms minimum poll interval.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9ATgshxvKbASTKzb2uG9F
…d2pt

feat(utils): new @qwik.dev/utils package with usePoll hook
@maiieul
maiieul merged commit cd9700c into main Aug 17, 2026
48 checks passed
@maiieul
maiieul deleted the utils-lib branch August 17, 2026 13:23
@github-project-automation github-project-automation Bot moved this from Waiting For Review to Done in Qwik Development Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants