Skip to content

feat: hydratable and a more consistent remote functions model#15533

Merged
elliott-with-the-longest-name-on-github merged 41 commits intomainfrom
elliott/remote-functions-hydratable-take-2
Mar 17, 2026
Merged

feat: hydratable and a more consistent remote functions model#15533
elliott-with-the-longest-name-on-github merged 41 commits intomainfrom
elliott/remote-functions-hydratable-take-2

Conversation

@elliott-with-the-longest-name-on-github
Copy link
Contributor

@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github commented Mar 11, 2026

This PR makes a number of substantial changes to how remote queries work.

hydratable

Implementation-wise, it replaces our custom transport solution with hydratable for queries that are used during render -- this method of transport is more correct and prevents us from accidentally using stale data for queries in a small subset of cases. There's one breaking side effect of this: If you didn't render a query on the server, you can't render it during hydration.

It means this:

 <script>
  import { browser } from '$app/environment';
  const count = browser ? get_count() : null;
</script>

would throw during hydration because get_count tried to access cached data that did not exist. This is almost always an undesirable mistake: It means you introduced a waterfall on the client and blocked hydration. If you really do want this, you'd do something like this instead:

<script>
  import { onMount } from 'svelte';
  let count;
  onMount(() => {
    count = get_count();
  });
</script>

New rules around query usage

From now on, on the client, you can only access a query's data if that query is:

  1. created in a tracking context (i.e. at the top level of a script block, in a derived, in an effect, etc -- basically somewhere Svelte's reactivity system can "see" the query)
  2. the tracking context in which the query is created is still alive

It may be easier to illustrate when you can't create and use a query on the client:

  • universal load
  • event handlers
  • the top level of modules

We're making this change because otherwise it's basically impossible to reliably cache queries across your app. However, for most use cases, this probably doesn't cause any pain:

  • you can still use the methods that don't access query data anywhere: .refresh, .set, .withOverride are all available
  • if you need to get a query's data in a non-reactive context, you can make a one-off request to get that query's data with query().run(), which will return a plain old Promise resolving to your data

Bugfixes

  • queries are now wrapped in their own $effect.root when created. This prevents them from associating with their parent effect, making their usage more predictable when retrieved from the cache
  • .refresh is now a noop if there is no cached instance of the query, meaning you won't have a useless query request in some circumstances
  • caching / deduplicating should be more reliable, and it should be more obvious when you're trying to do something insane
  • there should now be no cases where stale cached data is used post-hydration (thank you, hydratable!)

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 95fdd23

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

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Minor

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

@svelte-docs-bot
Copy link

@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github marked this pull request as ready for review March 13, 2026 19:25
…rowser consoles

This commit fixes the issue reported at packages/kit/src/runtime/client/remote-functions/query.svelte.js:58

**Bug Explanation:**

Three debug console.log statements were left in the production code at:

*   Line 58: `console.log(cache_key + ' bypassed hydratable');`
*   Line 292: `console.log(this._key, 'serving from the cache');`
*   Line 295: `console.log(this._key, 'made it past the cache');`

These statements appear to be development debugging aids that were not removed before committing. The codebase has a clear pattern for console.log usage:

1.  In build/CLI tools (acceptable since those run in Node.js during development)
2.  Guarded by `DEV` checks for runtime code (e.g., line 671 in respond.js uses `if (DEV && ...) { console.log(...) }`)

The unguarded console.log statements in query.svelte.js would execute on every query run/cache check in production, spamming end users' browser consoles with internal debugging messages like "query-key serving from the cache" and "query-key bypassed hydratable".

**Fix:**

Removed all three debug console.log statements since they serve no purpose for production users and were clearly left in accidentally (evidenced by commit messages like "i think it work" indicating development-in-progress code).


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: elliott-with-the-longest-name-on-github <hello@ell.iott.dev>
Copy link
Member

@Rich-Harris Rich-Harris left a comment

Choose a reason for hiding this comment

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

LGTM! nice work. only unresolved comment is about whether we want to merge #15555 or not. i leave it up to you

@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github merged commit 03670ad into main Mar 17, 2026
29 checks passed
@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github deleted the elliott/remote-functions-hydratable-take-2 branch March 17, 2026 20:19
@github-actions github-actions bot mentioned this pull request Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants