Skip to content

Instantly share code, notes, and snippets.

@PatrickJS
Last active May 23, 2026 09:29
Show Gist options
  • Select an option

  • Save PatrickJS/becc6fe15b9dab0876b0a0ea6a8b874f to your computer and use it in GitHub Desktop.

Select an option

Save PatrickJS/becc6fe15b9dab0876b0a0ea6a8b874f to your computer and use it in GitHub Desktop.
RFC: Server Function Graphs and Coordinated Caching for Qwik v2

RFC: Server Function Graphs and Coordinated Caching for Qwik v2

Summary

This proposal adds a Qwik-native optimization path for server data, rendered component output, optimizer-produced component render symbols, and resumable execution state.

The simple authoring model is:

server$(fn)
component$(fn)
useAsync$(serverFn, props)
<Suspense>

Cache and optimization participation are configured in one server-only app config.

The goal is not to introduce a server/client component split. The goal is to keep one Qwik component model while giving Qwik enough structure to safely shortcut SSR work.

The important path is page SSR. A user requests a page, Qwik starts rendering the route, and optimized component boundaries can take faster paths when their cache entries are valid.

The cache model has four coordinated layers:

server$ result cache:
  getProduct({ productId: '123' }) -> { title, price }

useAsync$ state cache:
  ProductCard -> getProduct -> pending | resolved | rejected

component render-symbol registry/cache:
  ProductCard_component_[hash] is the reusable generated render function

rendered component HTML cache:
  ProductCard({ productId: '123' }) + resolved server data -> HTML + Qwik metadata

Conceptual page SSR flow:

request /products/123
-> start Qwik SSR for the route
-> reach ProductCard({ productId: '123' })

1. rendered component HTML cache hit
   write cached ProductCard HTML
   include serialized server data and Qwik resume metadata
   skip ProductCard execution
   usually skip getProduct() too

2. component HTML miss, server$ data hit
   reuse cached getProduct({ productId: '123' })
   run ProductCard_component_[hash] with props + cached server data
   write the HTML
   cache this rendered ProductCard variation when safe

3. component HTML miss, server$ data miss
   fan out missing server$ calls across all optimized components
   dedupe identical getProduct() calls within the request
   render each component when its data resolves
   cache server$ results and rendered component HTML when policy allows

4. component is not eligible or cache policy is unsafe
   render that component or subtree with normal Qwik SSR

ideal cache-hit page:
   route shell + cached component HTML fragments + Qwik metadata
   SSR work becomes mostly cache lookup and string output

The default developer-facing API stays small. The registry and cache behavior are framework/runtime work, configured from server-only app config.

The central idea is:

component$ + server$ + useAsync$ + Suspense

with server-only configuration deciding which server resources and component render symbols may participate in caching, fan-out, render-symbol reuse, and fallback.

Goals

  • Keep one Qwik component model.
  • Keep optimization opt-in centralized in server-only global config.
  • Keep cache policy centralized.
  • Keep full cache configuration server-only.
  • Support Redis or any async get/set cache store through an adapter contract.
  • Use in-memory caching by default, with Redis or other shared stores as opt-in.
  • Support optional server$ input/output schemas for cache hardening.
  • Generate cache manifests from optimizer analysis, similar in spirit to q-manifest.
  • Give useAsync$(serverFn, props) an optimized analyzable path.
  • Treat Suspense as the render-state boundary for pending, resolved, and rejected async output.
  • Add registries for fetchable server$ resources and component render symbols.
  • Support server function fan-out and dedupe.
  • Support rendered HTML caching.
  • Support cached pending/resolved/rejected useAsync$ states.
  • Support streaming coordination for pending Suspense states.
  • Support optimizer-produced component render symbols for simplified cacheable rendering.
  • Preserve Qwik resumability and serialized execution state.
  • Keep normal Qwik SSR as the correctness fallback.

Non-goals

  • Do not introduce server/client component types.
  • Do not copy a server/client component model from another framework.
  • Do not require a new render primitive for normal components.
  • Do not require GraphQL.
  • Do not require non-Node runtimes.
  • Do not make every component render-symbol reusable.
  • Do not expose cache configuration to the browser.
  • Do not make every app author design cache keys locally.
  • Do not require Redis or any specific external cache store.
  • Do not require client-fetched CDN, edge, or origin partial transport for the first version.

Relationship To Current Qwik

Qwik already does part of this.

Today, component$ and $ boundaries let the optimizer split code into QRL-addressable symbols. Qwik can serialize component state, subscriptions, event handlers, lexical captures, and component boundaries into HTML so the browser can resume without replaying full hydration.

For example, normal author code:

import { component$, useSignal } from '@qwik.dev/core';

export default component$(() => {
  const count = useSignal(0);

  return (
    <main>
      <p>Count: {count.value}</p>
      <p>
        <button onClick$={() => count.value++}>Click</button>
      </p>
    </main>
  );
});

can become optimizer output with:

a lazy component symbol
a lazy click-handler symbol
QRL references to the generated chunks
serialized signal references
DOM metadata needed for resume

The proposed cache registry builds on those existing Qwik mechanisms.

The difference is the registry and cache contract.

This proposal asks Qwik to make selected server$ resources and component render symbols addressable as cacheable resources:

server$ registry:
  stable server function identity
  input/output shape
  server-only context rules
  cache policy metadata
  RPC/fetch entry

component render registry:
  stable component/render identity
  props shape
  server dependencies
  optimizer-produced render symbol
  rendered HTML cache metadata
  client reuse metadata

Current QRLs answer:

where is the lazy symbol needed to resume or handle this interaction?

This RFC adds:

what server data does this component render symbol need?
can the server result be cached?
can the component render symbol be fetched?
can rendered HTML be cached?
can the browser reuse this component render symbol with SSR data or local data?

Existing Output Plus Proposed Registry

Qwik already emits separate symbols for server functions, component render functions, and async closures.

Author code:

import { component$, Suspense, useAsync$ } from '@qwik.dev/core';
import { server$ } from '@qwik.dev/router';

export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

export const ProductCard = component$((props: { productId: string }) => {
  const product = useAsync$(() => getProduct({ productId: props.productId }));

  return (
    <Suspense fallback={<article class="p-4 border rounded-xl">Loading product...</article>}>
      <article class="p-4 border rounded-xl">
        <h2>{product.value.title}</h2>
        <p>{product.value.price}</p>
      </article>
    </Suspense>
  );
});

Current optimizer output is already close to the shape this proposal needs:

getProduct_server_[hash]
ProductCard_component_[hash]
ProductCard_component_product_useAsync_[hash]

Very simplified current-style output:

// app.tsx_getProduct_server_HJT5CQtaj48.js
export const getProduct_server_HJT5CQtaj48 = null;
// app.tsx_ProductCard_component_A0ecd0coGyE.js
const q_ProductCard_component_product_useAsync_tgGfLIY04nQ = qrlDEV(
  () => import('./app.tsx_ProductCard_component_product_useAsync_tgGfLIY04nQ.js'),
  'ProductCard_component_product_useAsync_tgGfLIY04nQ'
);

export const ProductCard_component_A0ecd0coGyE = (props) => {
  const product = useAsyncQrl(q_ProductCard_component_product_useAsync_tgGfLIY04nQ);

  return renderSuspense(product, {
    pending: () => _jsxSorted('article', null, { class: 'p-4 border rounded-xl skeleton' }, [
      _jsxSorted('h2', null, null, 'Loading product...')
    ]),
    rejected: () => _jsxSorted('article', null, { class: 'p-4 border rounded-xl error' }, [
      _jsxSorted('h2', null, null, 'Product unavailable')
    ]),
    resolved: () => _jsxSorted('article', null, { class: 'p-4 border rounded-xl' }, [
      _jsxSorted('h2', null, null, _fnSignal((p) => p.value.title, [product])),
      _jsxSorted('p', null, null, _fnSignal((p) => p.value.price, [product])),
    ]),
  });
};
// app.tsx_ProductCard_component_product_useAsync_tgGfLIY04nQ.js
import { getProduct } from './app.tsx';

export const ProductCard_component_product_useAsync_tgGfLIY04nQ = (props) => {
  return getProduct({ productId: props.productId });
};

Without this RFC's cache registry, normal SSR can still render correctly:

request
-> run ProductCard_component_A0ecd0coGyE
-> load ProductCard_component_product_useAsync_tgGfLIY04nQ
-> call getProduct
-> render JSX output
-> serialize Qwik resume metadata

The missing part is not code splitting. Qwik already has that.

The missing part is cache-aware coordination:

which server$ symbol can be cached?
which component render symbol depends on it?
which async QRL connects them?
can repeated ProductCard instances fan out and dedupe getProduct?
can rendered ProductCard HTML be reused safely?

Conceptually:

getProduct_server_[hash]
  = server resource symbol

ProductCard_component_[hash]
  = component render symbol

ProductCard_component_product_useAsync_[hash]
  = async edge from component render symbol to server resource symbol

This RFC adds metadata around those symbols:

server resource registry:
  getProduct_server_[hash]
  policy: defaultResource
  input: { productId }

component render registry:
  ProductCard_component_[hash]
  policy: defaultComponent
  async edges: [getProduct_server_[hash]]
  suspense states: [pending, resolved, rejected]
  signal reads: [product.value.title, product.value.price]

With that registry, Qwik can fan out server work:

ProductList
|- ProductCard({ productId: '1' })
|  |- getProduct_server_[hash]({ productId: '1' })
|- ProductCard({ productId: '2' })
|  |- getProduct_server_[hash]({ productId: '2' })
|- ProductCard({ productId: '1' })
   |- reuse in-flight/cache entry for productId '1'

and it can fan out component rendering:

resolve server resources
-> render independent ProductCard_component_[hash] instances
-> cache safe rendered HTML entries
-> stream or reuse the results

The proposal is not that Qwik needs a new output format. It is that Qwik should promote selected existing symbols into a cache-aware registry when the component is simple enough.

A runnable local simulation is included in:

examples/qwik-cache-registry-demo/

Simple API

Server-only global cache setup

Cache policy and optimization targets are configured once in a server-only app config.

The full config is server-only. It is loaded by the Qwik server runtime, router adapter, or build integration. It is not imported into browser-visible component code.

// src/cache.server.ts
import { defineCacheConfig } from '@qwik.dev/router/cache';
import { memoryStore } from '@qwik.dev/router/cache/memory';
import { redisStore } from '@qwik.dev/router/cache/redis';
import { ProductCard } from './product-card';
import { getProduct, getSegment } from './products';
import { productInput, productOutput } from './products.schema';

export default defineCacheConfig({
  stores: {
    memory: memoryStore(),

    // Optional shared store for production or multi-process deployments.
    redis: redisStore({
      url: process.env.REDIS_URL,
    }),
  },

  optimize: {
    resources: {
      getProduct: {
        target: getProduct,
        policy: 'defaultResource',
        input: productInput,
        output: productOutput,
        vary: [getSegment],
      },
    },

    components: {
      ProductCard: {
        target: ProductCard,
        policy: 'defaultComponent',
        vary: [getSegment, getProduct],
        ssrState: 'auto',
      },
    },
  },

  defaults: {
    resources: {
      store: 'memory',
      scope: 'request',
      dedupe: true,
    },

    components: {
      store: 'memory',
      scope: 'private',
      ttl: '5m',
      staleWhileRevalidate: '30s',
    },
  },
});

optimize.resources and optimize.components are the opt-in surface:

the app centrally names which server$ resources may be cached and deduped
the app centrally names which components may use render-symbol reuse and rendered HTML caching
server$ functions in vary provide cache key variation
the optimizer still decides whether each target is safe

In this RFC, resources means server$ resources. The name leaves room for future virtual or remote server resources without changing the component authoring model.

This avoids changing normal server$ or component$ authoring. Defaults must be conservative and private-safe.

The default store can be in-memory so the feature works without external infrastructure. Redis or another shared store becomes an opt-in production choice when an app needs cross-request, cross-process, or multi-region reuse.

Server function

import { server$ } from '@qwik.dev/router';

export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

Optional input/output hardening

Qwik can infer TypeScript input and output shapes, but cache safety benefits from runtime validation when values cross a registry boundary.

The simple path should remain inferred:

export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

Advanced apps can add explicit input/output schemas in server-only cache config:

import { productInput, productOutput } from './products.schema';

export default defineCacheConfig({
  optimize: {
    resources: {
      getProduct: {
        target: getProduct,
        policy: 'defaultResource',
        input: productInput,
        output: productOutput,
      },
    },
  },
});

This lets the runtime harden:

cache key generation
RPC/server$ request decoding
cached value decoding
manifest compatibility checks
development warnings for shape drift
strict fallback when a cached value no longer matches the declared output

A colocated server function form could also be supported:

export const getProduct = server$({
  input: productInput,
  output: productOutput,
}, async function ({ productId }) {
  return { title: 'Keyboard', price: '$199' };
});

The preferred first path is server-only config because it keeps normal author code as server$(fn) and keeps cache hardening centralized.

Component

import { component$, Suspense, useAsync$ } from '@qwik.dev/core';

export const ProductCard = component$((props: { productId: string }) => {
  const product = useAsync$(getProduct, props);

  return (
    <Suspense fallback={<article class="p-4 border rounded-xl">Loading product...</article>}>
      <article class="p-4 border rounded-xl">
        <h2>{product.value.title}</h2>
        <p>{product.value.price}</p>
      </article>
    </Suspense>
  );
});

This remains a normal Qwik component. The centralized config says this exported component may participate in coordinated cache and render-symbol reuse when safe.

Why central optimization config

Most developers should not need to design cache policy inside every server function or component.

Global server config gives Qwik a server-owned opt-in signal:

these server$ resources may participate in coordinated caching
these components may participate in graph analysis, render-symbol reuse, rendered HTML caching, and fallback

This makes sense for both sides of the graph:

server$ resources need centralized cache policy, vary rules, tags, and request-context safety
components need centralized render-symbol eligibility, rendered HTML policy, and fallback diagnostics

Putting both in one server-only config lets the framework build one consistent graph:

component target
-> async QRL edge
-> server$ resource target
-> cache policy and vary rules

This keeps local code unchanged:

server$(fn)
component$(fn)

Why useAsync$(serverFn, props) Gets A Fast Path

The callback form remains valid for correctness:

const product = useAsync$(async () => {
  return getProduct({ productId: props.productId });
});

However, the callback form is opaque to the optimizer. Qwik can run it, but it is harder to infer a safe server-function graph or rendered-component cache key.

The direct server-function form is the optimized path:

const product = useAsync$(getProduct, props);

It gives Qwik a clear graph edge:

ProductCard
-> getProduct
-> props
-> Suspense pending/resolved/rejected states
-> product.value reads when resolved

From that edge, Qwik can infer:

component depends on server function
server function input comes from component props
server function result is read by the component render output
component HTML may be cacheable
component render symbol may be reusable
request work can be deduped and fanned out
SSR values can seed SPA navigation
Suspense fallback/error/data output can be cached separately

This special case exists only to provide an optimized path for cached component output and render-symbol reuse. If Qwik cannot prove the optimized path is safe, it falls back to normal Qwik SSR.

Simplified Mental Model

Developers still write one Qwik component.

const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

export const ProductCard = component$((props: { productId: string }) => {
  const product = useAsync$(getProduct, props);

  return (
    <Suspense fallback={<article>Loading product...</article>}>
      <article>
        <h2>{product.value.title}</h2>
        <p>{product.value.price}</p>
      </article>
    </Suspense>
  );
});

Qwik can internally split this into two optimized pieces:

server$ = server data function
component render symbol = reusable render function

This is similar to older server-rendered web architecture:

server data function -> fetch data
render artifact      -> render HTML from data

but it keeps the Qwik authoring model:

component$ + server$ + useAsync$ + Suspense

Server function as RPC resource

server$ already gives Qwik a server-only execution boundary.

Conceptually:

const getProductRpc = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

The server function can be:

called during SSR
called during SPA navigation
deduped during one request
cached using its own cache key
executed with server request context

Its identity is separate from the rendered component:

server:getProduct
|productId:123
|policy:defaultResource
|manifest:abc123

Component render symbol as reusable output

Qwik already outputs component render symbols and lazy chunks.

The important idea is the split:

server$ owns server data and request context
component render symbol owns serializable rendering from data to HTML/Qwik output

When the component is safe, Qwik can treat its existing optimizer output as a reusable render artifact.

Qwik already does fine-grained frontend code splitting. This proposal applies the same idea to cacheable component output: split server data from the component render symbol so each piece can be cached and reused independently.

The simple API still starts from component$:

export const ProductCard = component$((props: { productId: string }) => {
  const product = useAsync$(getProduct, props);

  return (
    <article class="p-4 border rounded-xl">
      <h2>{product.value.title}</h2>
      <p>{product.value.price}</p>
    </article>
  );
});

Conceptually, Qwik can lower that into a server-only resource and a shared component render symbol.

Server-only resource:

// product.server.ts
import { server$ } from '@qwik.dev/router';

export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});

Optimizer-produced render symbol:

// product-card.tsx_ProductCard_[hash].js
export const product_card_ProductCard_[hash] = ({ props, server }) => {
  return _jsxSorted('article', { class: 'p-4 border rounded-xl' }, null, [
    _jsxSorted('h2', null, null, server.product.title),
    _jsxSorted('p', null, null, server.product.price),
  ]);
};

The exact generated code can use Qwik's real internal helpers. The important contract is that the render symbol receives serializable input and does not own the server request context.

The render symbol can be:

used on the server to render faster HTML
cached as a JavaScript render-symbol artifact
sent to the client if needed
reused with SSR-seeded server data
reused with compatible local client data
skipped when the component is too dynamic

Its component identity is separate from the server function:

component:ProductCard
|renderSymbolVersion:v4
|manifest:abc123

Why the split matters

The direct useAsync$(serverFn, props) form gives Qwik the edge needed to make this split:

ProductCard(props)
-> getProduct(props)
-> product_card_ProductCard_[hash]({ product })

That means Qwik can cache each layer independently:

server function result cache:
  getProduct({ productId: '123' })

generated component render symbol cache:
  product_card_ProductCard_[hash]

rendered HTML cache:
  ProductCard({ productId: '123' }) using getProduct result

The same split also helps the browser:

if client has product data locally:
  run cached ProductCard render symbol with local data

if client has render symbol but not data:
  RPC getProduct, then run ProductCard render symbol

if client has neither or the render path is unsafe:
  ask server for rendered HTML or use normal Qwik SSR/navigation fallback

The frontend may choose a local reuse policy for client-owned data and client-side render-symbol caches. That policy can decide whether local data is fresh enough for SPA rendering.

It must not override server cache policy or expose server cache configuration.

The mental model remains server-safe:

server$ owns server request context
component render symbols consume serializable data
cache config stays server-only
normal Qwik SSR remains the fallback

Server-only Cache Configuration

The full cache configuration must never be exposed to the browser.

It must not be:

serialized into Qwik state
emitted into HTML
included in client JavaScript chunks
exposed through SPA manifests
read by browser code

Only opaque derived metadata may cross to the browser:

resource IDs
component/render IDs
cache key IDs
manifest/version IDs
safe freshness metadata
render-symbol references intended for the client

The compiler and router should enforce this:

CacheConfig imports are server-only
global cache config is consumed by SSR/runtime adapters
importing cache config into client-visible modules is a dev/build error
passing cache config into client-visible props is a dev/build error
secrets, stores, credentials, and environment values in config are never serialized
browser code receives opaque metadata only

If a route is rendered only on the client, it cannot inspect cache config. It can only use server-provided opaque cache metadata from SSR or navigation responses.

Cache Store Adapter Contract

The cache service should support Redis, in-memory stores, platform KV stores, platform cache stores, and app-defined stores through a small async get/set contract.

Initial adapter shape:

export interface CacheStore {
  get(key: string): Promise<CacheRecord | null>;
  set(key: string, value: string | Uint8Array, options: CacheSetOptions): Promise<void>;
  delete(key: string): Promise<void>;
  invalidateTag?(tag: string): Promise<void>;
}

export type CacheRecord = {
  value: string | Uint8Array;
  metadata?: Record<string, string | number | boolean>;
};

export type CacheSetOptions = {
  namespace: string;
  scope?: 'request' | 'private' | 'public';
  ttlMs?: number;
  staleWhileRevalidateMs?: number;
  tags?: string[];
};

Qwik should own serialization before values reach the adapter.

The adapter stores stable strings or bytes for:

server$ result envelopes
useAsync$ pending/resolved/rejected envelopes
rendered component HTML fragments
Qwik resumability metadata needed for cached HTML
page or route fragments when policy allows

This keeps stores simple:

memory adapter
Redis adapter
platform KV adapter
custom get/set adapter

Optional tag invalidation can be supported through invalidateTag.

If a policy uses tags but the selected store does not support tag invalidation, Qwik should keep TTL/delete behavior and warn in development.

Generated Cache Manifest And Overrides

Qwik can generate a cache manifest from optimizer analysis, similar in spirit to q-manifest.

The generated cache manifest can include:

manifest hash
server$ resource identities
component render symbol identities
async QRL edges
useAsync$ state shapes
detected server$ dependencies
derived key inputs
derived vary candidates
explicit input/output schemas when configured
derived public/private safety candidates
component render-symbol versions
safe default eligibility
Qwik metadata versions needed for resumability

The app's server-only config then overrides Qwik's derived defaults:

Qwik analysis
-> generated cache manifest
-> server-only user config overrides
-> runtime policy

User config wins for:

store choice
TTL
scope
tags
named policy
input/output schema hardening
vary server$ resources
component ssrState
explicit optimize targets

Qwik safety analysis still gates optimized execution.

If user config requests a component cache path that Qwik cannot prove safe, Qwik should warn and use normal Qwik SSR.

The full cache manifest is server-runtime metadata. The browser may receive only an opaque, public-safe subset such as manifest version IDs, resource IDs, component render IDs, and freshness metadata.

Advanced Configuration

The simple path should be enough for most apps:

export default defineCacheConfig({
  optimize: {
    resources: {
      getProduct: { target: getProduct, policy: 'defaultResource' },
    },
    components: {
      ProductCard: { target: ProductCard, policy: 'defaultComponent' },
    },
  },
});

Advanced configuration refines that default.

Vary by server functions

Server functions can compose.

const getSegment = server$(async function () {
  const cookie = this.request.headers.get('cookie') ?? '';
  return { plan: cookie.includes('pro') ? 'pro' : 'free' };
});

Another server function can vary by it:

const getPricing = server$(async function ({ productId }: { productId: string }) {
  const segment = await getSegment();

  return {
    price: segment.plan === 'pro' ? '$19' : '$29',
  };
});

A component can vary rendered HTML by server function output:

export const PricingCard = component$((props: { productId: string }) => {
  const pricing = useAsync$(getPricing, props);

  return (
    <p class="p-4 border rounded-xl">
      {pricing.value.price}
    </p>
  );
});

The vary relationship lives in server-only config:

export default defineCacheConfig({
  optimize: {
    resources: {
      getSegment: {
        target: getSegment,
        policy: 'privateServer',
      },
      getPricing: {
        target: getPricing,
        policy: 'defaultResource',
        vary: [getSegment],
      },
    },
    components: {
      PricingCard: {
        target: PricingCard,
        policy: 'privateUser',
        vary: [getSegment, getPricing],
        ssrState: 'auto',
      },
    },
  },
});

vary is not required for normal SSR correctness. Normal Qwik SSR already runs the code and produces correct HTML.

vary exists to provide a safe optimized path:

if these server function outputs change, this cache entry may change

The functions in vary are normal server$ functions. They run on the server today and can later feed future placement work, such as edge or CDN-backed partials, if they are portable.

If Qwik cannot prove the cache key is safe, it should skip cached component execution and use normal SSR.

Named policies

export default defineCacheConfig({
  stores: {
    memory: memoryStore(),
    redis: redisStore({
      url: process.env.REDIS_URL,
    }),
  },

  defaults: {
    resources: {
      store: 'memory',
      scope: 'request',
      dedupe: true,
    },

    components: {
      store: 'memory',
      scope: 'private',
      ttl: '5m',
    },
  },

  policies: {
    publicProduct: {
      store: 'redis',
      scope: 'public',
      ttl: '10m',
      staleWhileRevalidate: '30s',
    },

    privateUser: {
      store: 'redis',
      scope: 'private',
      ttl: '60s',
    },
  },
});

Usage:

export default defineCacheConfig({
  optimize: {
    resources: {
      getProduct: {
        target: getProduct,
        policy: 'publicProduct',
        tags: ['product'],
      },
      getSegment: {
        target: getSegment,
        policy: 'privateUser',
      },
    },
    components: {
      ProductCard: {
        target: ProductCard,
        policy: 'privateUser',
        vary: [getSegment],
      },
    },
  },
});

Scoped route or layout policy overrides

The global server config can support scoped policy overrides for routes, layouts, or subtrees.

export default defineCacheConfig({
  scopes: {
    '/dashboard': {
      policy: 'privateUser',
    },
  },
});

This lets an app express:

root defaults
route/layout overrides
component/server function opt-in

A provider-like wrapper could be considered later for subtree ergonomics, but it should be a framework-recognized server/cache primitive, not ordinary client-visible context.

Cache Keys

Cache keys must include every value that can affect output.

A server function cache key should include:

server function identity
server function version
input values
declared vary server function outputs
cache policy version
manifest version
scope
store namespace

A rendered component cache key should include:

component identity
component version
props
server function dependencies
declared vary outputs
component render symbol version when used
cache policy version
manifest version
scope
store namespace

Incorrect cache keys can leak private data or serve stale HTML. Cache key generation should be centralized, inspectable in development, and compatible with strict mode.

Request Fan-out And Deduplication

Fan-out means one request can trigger multiple downstream async resources in parallel.

Example:

export const ProductList = component$((props: { ids: string[] }) => {
  return (
    <div class="space-y-4">
      {props.ids.map((productId) => (
        <ProductCard key={productId} productId={productId} />
      ))}
    </div>
  );
});

Each ProductCard uses:

const product = useAsync$(getProduct, props);

Qwik can infer a runtime graph:

ProductList
|- ProductCard({ productId: '1' }) -> getProduct({ productId: '1' })
|- ProductCard({ productId: '2' }) -> getProduct({ productId: '2' })
|- ProductCard({ productId: '3' }) -> getProduct({ productId: '3' })

The request orchestrator can:

run independent server functions in parallel
dedupe identical server function calls
share one in-flight promise per server function key
share one cache entry per server function key
stream component HTML as data resolves
serialize resolved server function values for resume
reuse values during SPA navigation

If multiple components request the same server function with the same input:

getProduct({ productId: '1' })

Qwik should run it once for the request.

All consumers should share:

same in-flight request
same resolved value
same cache entry when policy allows
same serialized execution state

Does this replace DataLoader?

For the common SSR case, Qwik should not need a separate public data-loader primitive.

This section uses "DataLoader" in the GraphQL sense: a request-scoped utility that batches and caches backend loads to avoid repeated or N+1 data fetches. The common JavaScript reference is graphql/dataloader.

The server function graph already gives Qwik enough information to handle the most important request-level behavior:

same server$ + same input:
  dedupe within the request
  share one in-flight promise
  share one resolved value
  share one cache entry when policy allows

That covers the repeated component case:

ProductCard({ productId: '1' }) -> getProduct({ productId: '1' })
ProductCard({ productId: '1' }) -> reuse the same getProduct result

Batching different inputs is a separate concern.

For example:

getProduct({ productId: '1' })
getProduct({ productId: '2' })
getProduct({ productId: '3' })

Qwik can fan these out in parallel and cache each result independently. If the app wants one database query instead of three, that batching can live inside server$:

export const getProducts = server$(async function ({ ids }: { ids: string[] }) {
  return db.products.findMany({ ids });
});

or inside the implementation of a server function:

export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return this.loaders.products.load(productId);
});

A DataLoader-style utility may still be useful because it solves backend-specific problems that the Qwik graph should not have to understand:

batching different keys into one SQL query
avoiding N+1 reads against a database or CMS
respecting backend query limits and max batch sizes
preserving result order for batched keys
sharing request-scoped authorization and tenant filters
coalescing calls made by lower-level service functions
normalizing backend-specific error behavior

For example, Qwik can see that three components need getProduct, but it should not need to know whether the backend prefers:

SELECT * FROM products WHERE id IN (...)
POST /products/batch
GraphQL query with many IDs
CMS SDK batch lookup

That belongs behind the server function boundary.

The distinction is:

Qwik graph/runtime:
  dedupe identical server$ calls
  fan out independent server$ calls
  cache server$ results
  coordinate component rendering

server$ implementation:
  choose database queries
  use DataLoader-style batching if useful
  call REST, SQL, GraphQL, CMS, or other services

So a DataLoader-style utility can still be useful inside server$, especially for backend batching. It should not be required as a new Qwik authoring primitive for the cache registry model.

Component Render Symbols

A component render symbol is the optimizer-produced render half of a cacheable component.

Qwik already emits component symbols into JavaScript chunks. This proposal uses those existing emitted symbols as the reusable render artifact when the component is safe for cacheable rendering.

The simple API does not require a new authoring primitive.

Author code:

export const ProductCard = component$((props: { productId: string }) => {
  const product = useAsync$(getProduct, props);

  return (
    <article class="p-4 border rounded-xl">
      <h2>{product.value.title}</h2>
      <p>{product.value.price}</p>
    </article>
  );
});

Equivalent conceptual split:

// product.server.ts
export const getProduct = server$(async function ({ productId }: { productId: string }) {
  return { title: 'Keyboard', price: '$199' };
});
// product-card.tsx_ProductCard_[hash].js
export const product_card_ProductCard_[hash] = ({ props, server }) => {
  return _jsxSorted('article', { class: 'p-4 border rounded-xl' }, null, [
    _jsxSorted('h2', null, null, server.product.title),
    _jsxSorted('p', null, null, server.product.price),
  ]);
};

In this split:

server$ is the RPC/data boundary
component render symbol is the reusable render boundary
component$ is the normal authoring model that can be lowered into both

The server can cache:

server$ results
component render symbol modules
rendered HTML from server$ result + component render symbol

The browser can reuse:

component render symbol module sent by the server
server$ data sent by SSR or SPA navigation
compatible local data already available in the client

This enables data/render separation without changing the default Qwik mental model:

server$ ~= server data function
component render symbol ~= render artifact
component$ ~= author-friendly source that connects them

The real generated or extracted render output must preserve:

Qwik metadata
QRL references
serialized state
scoped styles
manifest hashes
resumability markers
server function values needed for resume

Qwik decides whether a cached component is safe for render-symbol execution. Unsafe components use normal Qwik SSR.

Server And Component Registries

The optimized path needs registries for both sides of the split.

Server registry

The server registry makes selected server$ functions addressable.

Example conceptual entry:

{
  id: 'server:getProduct',
  symbol: './product.server.js#getProduct',
  input: '{ productId: string }',
  output: '{ title: string; price: string }',
  inputSchema: 'productInput',
  outputSchema: 'productOutput',
  policy: 'defaultResource',
  context: 'server-only'
}

This lets Qwik:

RPC the server function during SPA navigation
validate inputs and cached outputs when schemas are configured
dedupe identical calls during SSR
cache server results by input and vary rules
serialize resolved values into SSR output
hide server request context from the browser

Component render registry

The component render registry makes selected optimizer-produced render symbols addressable.

Example conceptual entry:

{
  id: 'component:ProductCard',
  component: './product-card.js#ProductCard',
  symbol: './product-card.js#product_card_ProductCard_[hash]',
  props: '{ productId: string }',
  server: ['server:getProduct'],
  policy: 'defaultComponent'
}

This lets Qwik:

fetch a component render symbol when the browser needs it
render on the server using cached server data
render in the browser using SSR-seeded data
render in the browser using compatible local data
fall back to normal component SSR when unsafe

The registry is not a replacement for QRLs.

It builds on the same idea:

QRLs identify lazy symbols.
registries describe which lazy symbols are safe cache/fetch/render resources.

Fetchable component render path

The browser should be able to fetch the component render symbol the way it can call the server resource.

Conceptual flow:

1. browser navigates to /products/123
2. route manifest says ProductCard needs getProduct and product_card_ProductCard_[hash]
3. browser checks local data cache
4. browser checks local component render module cache
5. missing data -> RPC server:getProduct
6. missing render symbol -> fetch component:ProductCard
7. render product_card_ProductCard_[hash] with server data or compatible local data

This lets Qwik keep the server mental model:

server$ still runs on the server with request context
component render symbol can run anywhere if it has serializable input
component$ remains the source authoring model

Component Cache Story

The component cache has two different layers.

First, Qwik has the component render symbol:

ProductCard_component_[hash]

This is the reusable generated render function. Caching or registering this symbol means Qwik knows:

which component it came from
which props shape it expects
which server$ resources it needs
which Qwik metadata version it emits
whether it is safe to call without full normal SSR

This layer is about reusable code and metadata. It is not the final rendered output for a specific product.

Second, Qwik has rendered component HTML:

ProductCard({ productId: '123' })
+ getProduct({ productId: '123' })
+ getSegment()
= HTML + serialized server values + Qwik resume metadata

This is the actual component output cache. Its key must include the component identity, props, server$ dependencies, vary outputs, render-symbol version, policy version, and manifest version.

The component cache shortcut is:

if rendered component HTML is cached:
  return that HTML and Qwik metadata immediately

else if server$ data is cached:
  run the component render symbol with cached data
  store the rendered component HTML if safe

else:
  fan out and dedupe the missing server$ calls
  run the component render symbol when the data resolves
  store server$ results and rendered component HTML if policy allows

if any step is unsafe:
  use normal Qwik SSR

This is why the registry matters. Qwik is not only caching data. It is coordinating:

server$ result cache
useAsync$ pending/resolved/rejected state cache
component render-symbol registry
rendered component HTML cache
Qwik resumability metadata

Runtime Behavior

For a globally optimized component, Qwik uses a fallback ladder. Each step either returns a safe cached result or falls through to the next step.

1. Rendered HTML cache
   Key: component + props + server$ dependencies + vary rules.
   Hit: return cached HTML and the Qwik metadata needed to resume.
   This is the fastest component-cache path because Qwik does not need
   to re-run the component render function.

2. useAsync$ state cache
   Key: server$ resource + input + vary rules.
   Hit pending: render the cached Suspense fallback immediately.
   Hit resolved: reuse the cached server$ value.
   Hit rejected: render a safe cached error state when policy allows.

3. Request fan-out and server$ dedupe
   If data is not cached, run independent server$ resources in parallel.
   Identical server$ calls share one in-flight promise and one result.

4. Component render symbol
   If server data is resolved and the component is eligible, call the
   optimizer-produced render symbol with props and resolved server data.
   Then cache the rendered HTML and metadata when policy allows.
   This is the component-cache rebuild path: data may be cached while the
   final rendered HTML variation is not cached yet.

5. Normal Qwik SSR fallback
   If analysis, cache policy, serialization, or metadata safety fails,
   render exactly the way Qwik renders today.

Normal Qwik SSR is always the correctness fallback.

useAsync$ State Cache And Suspense

The optimized path should cache the async state envelope, not only the final data.

For each useAsync$ edge, Qwik can track:

resource identity
input values
async status: pending | resolved | rejected
safe payload, value key, or error key
Suspense boundary output
Qwik resume metadata

That means each state can have a safe fast path:

pending:
  serve cached Suspense fallback HTML immediately
  keep or dedupe the in-flight server$ work

resolved:
  serve cached server$ value
  run or reuse the component render symbol
  optionally serve cached rendered component HTML

rejected:
  serve safe cached error output only when policy allows it
  avoid caching private auth errors or transient server failures by default

This lets SSR shortcut work even before data has resolved. If a resource is known to be pending and the same Suspense fallback was already rendered safely, Qwik can return that fallback immediately instead of redoing the pending render path.

For streaming coordination, the pending state can be served in the initial HTML while the server keeps resolving the same server$ work:

initial HTML:
  Suspense pending boundary
  opaque boundary ID

later server output:
  resolved HTML or normal Qwik streamed update
  same boundary ID
  Qwik metadata needed to resume

A later client-fetched partial system can reuse the same state envelope and boundary IDs, but it does not need to be part of the first cache-registry proposal.

If every optimized component and async state is a cache hit, SSR can become mostly concatenation of validated SSR fragments:

route shell fragment
+ cached pending/resolved/error component fragments
+ serialized Qwik metadata
+ manifest and symbol references

This is not plain HTML templating. Cached fragments must preserve the same Qwik SSR contract normal rendering would emit.

Component ssrState For Personalization

Components can choose how the optimized SSR path handles async state.

The server-only component config can use:

export default defineCacheConfig({
  optimize: {
    components: {
      GreetingCard: {
        target: GreetingCard,
        policy: 'privateUser',
        vary: [getUserKey],
        ssrState: 'auto',
      },

      Recommendations: {
        target: Recommendations,
        policy: 'privateUser',
        vary: [getUserKey],
        ssrState: 'pending',
      },
    },
  },
});

ssrState: 'auto' is the default.

It lets Qwik render resolved output when cache scope, vary rules, and analysis are safe. This supports private personalized SSR for safe data, such as a user's name, when the private user key is part of the cache key.

ssrState: 'pending' forces SSR to emit the Suspense pending state for that component.

That is useful when:

personalized data should be computed in the browser
server output should avoid embedding user-specific data
the client has local data that can fill the component after resume
the app wants a fast cached shell while personalization resolves locally

This is a component policy by default. A server$ resource can still use private server caching when it has safe key and vary rules.

Simple Component Eligibility

Only simple, analyzable component render symbols should use the optimized cache path.

Allowed patterns should include:

serializable props
static or analyzable class/style output
deterministic JSX
deterministic loops and conditionals
signal reads that Qwik can serialize and track
store reads that Qwik can serialize and track
useAsync$(serverFn, props)
simple useAsync$ callbacks that call one cached server$
Qwik event metadata and QRLs

Signals should be fine when they follow normal Qwik serialization rules. The existing optimizer already represents signal reads with helper functions such as _fnSignal(...), and Qwik already serializes resumable state. For cached rendered HTML, the cache entry must include the same Qwik metadata and serialized signal/resource state needed to resume correctly.

Examples that should stay eligible:

const product = useAsync$(getProduct, props);

return (
  <article>
    <h2>{product.value.title}</h2>
    <p>{product.value.price}</p>
  </article>
);
const expanded = useSignal(false);

return (
  <button onClick$={() => expanded.value = !expanded.value}>
    {expanded.value ? 'Hide' : 'Show'}
  </button>
);

The signal example can still use normal Qwik resumability. Render-symbol reuse is safe only if Qwik can preserve the same serialized signal state and subscriptions that normal SSR would emit.

Disallowed or fallback patterns should include:

browser APIs during SSR
Date.now() or Math.random() during render
side effects during render
request/cookie/header reads inside component render
non-serializable signal/store values
opaque async callbacks that hide server dependencies
hidden auth or permission checks
dynamic imports or dynamic code the optimizer cannot analyze

When a component is not eligible, Qwik should not fail the app. It should use normal Qwik SSR and resumability.

Lint And SSR Diagnostics

The optimizer should have a cache-eligibility lint pass.

At build time, it should identify:

globally optimized server resources
globally optimized components
component render symbols
async QRLs inside component render symbols
server resource symbols called by those async QRLs
signal/store reads that affect rendered output
unsupported render patterns

If a component is listed in optimize.components but is not eligible, the linter should warn:

ProductCard is listed for cache optimization, but normal SSR will be used.
Reason: useAsync$ callback is opaque; use useAsync$(getProduct, props) or a simple direct server$ call.

During SSR, Qwik should also log a development warning when it skips the optimized path:

[qwik-cache] ProductCard_component_[hash] skipped cached render path: non-deterministic render expression. Falling back to normal Qwik SSR.

These warnings are not correctness errors. They are performance guidance for developers who opted a component or server resource into the cache registry and want to make that path eligible.

For example, a developer can use the warnings to refactor:

opaque useAsync$ callback
-> use useAsync$(getProduct, props)

Date.now() in render
-> move time calculation into server$ or props

request header read inside component render
-> move request-dependent logic into server$ and declare vary rules

The app should keep working even if the developer ignores the warning. The warning explains why Qwik used normal SSR instead of the optimized cache path.

Production should preserve correctness and avoid noisy logs by default. The fallback path should produce the same output Qwik produces today.

SSR And SPA Coordination

SSR and SPA navigation should share the same execution model.

An SSR response can include:

HTML
serialized server function values
pending Suspense boundary IDs
opaque component/resource cache IDs
manifest/version IDs
component render-symbol references
Qwik resumability metadata

The browser can seed local runtime caches from opaque metadata:

server function values
rendered component entries when safe
component render-symbol executor modules
route orchestration metadata

The browser must not receive full cache policy configuration.

During SPA navigation, the client can:

check local rendered component cache
check local server function value cache
download missing component render executors once
request missing server function values
render locally when it has both data and a compatible render symbol
request server-rendered component HTML when local execution is unavailable

The browser is allowed to use local data and a fetched component render symbol for SPA rendering. It is not allowed to reinterpret server cache scope, public/private safety, or vary rules. Those decisions remain server-owned.

Client-fetched partials from CDN, edge, or origin can build on this later by adding opaque partial references to SSR/navigation responses.

Safety Rules

Unsafe cases fall back to normal Qwik SSR:

browser APIs during SSR
Date.now() or Math.random() in render
hidden auth checks
private data marked public
non-serializable input/output
opaque async callbacks
untracked request/cookie/header reads
dynamic code the optimizer cannot analyze

Fallback must be automatic.

Development mode should explain why an optimized path was skipped.

Implementation Phases

Phase 1: simple API and metadata

Support:

server$(fn)
component$(fn)
useAsync$(serverFn, props)
server-only global cache config with optimize.resources and optimize.components
generated cache manifest with derived defaults

Emit metadata, but continue rendering through normal Qwik SSR.

Phase 2: server function graph

Build a graph from:

component tree
useAsync$(serverFn, props)
simple useAsync$ callbacks that directly call cached server$
optimizer-produced async QRLs
cache.vary dependencies

Support:

request dedupe
in-flight sharing
fan-out
tracing
dev diagnostics

Phase 3: cache eligibility lint

Add optimizer/lint diagnostics for:

components listed in optimize.components that are not render-symbol reusable
opaque useAsync$ callbacks
non-serializable signal/store values
browser-only APIs in render
non-deterministic render expressions
missing vary declarations for request-derived server data

Invalid optimized paths should produce warnings and fall back to normal Qwik SSR.

Phase 4: centralized cache service

Implement:

server-only cache config
cache store adapter contract
Redis/memory/custom get-set adapters
generated cache manifest merge
user config override precedence
optimize.resources and optimize.components defaults
named policies
request cache
server function cache
rendered component cache
optional tag invalidation
stale-while-revalidate

Phase 5: rendered HTML cache

Cache rendered component/subtree HTML with Qwik metadata.

Cached entries must include:

HTML
serialized server function values
Qwik resumability metadata
symbol references
component/render-symbol version
manifest hash
cache key inputs
store namespace

Phase 6: component render-symbol reuse

Emit or register JavaScript component render executors for safe components.

Use them in Node.js SSR before falling back to full component execution.

Phase 7: SSR and SPA coordination

Let the client:

cache component render executors
reuse SSR-seeded server function values
fetch missing component render symbols once
request server-rendered component HTML when needed

Future phase: client-fetched partial transport

This should be a later RFC or later phase after the cache and registry model is proven.

Possible support:

client-fetched partials from opaque references
public CDN or edge policies for safe segment/A-B data
private origin policies for user-only data
adapter mapping to platform cache headers and stores
partial batching so optimized pages do not create excessive request counts

This future phase can map the same cache registry and component render registry to real CDN, edge, origin, Redis, KV, or memory stores.

Future phase: optional alternate renderers

Emit stable manifests for future renderer experiments.

Alternate renderers can:

render registered component symbols
call Node server function fallback
call Node SSR fallback

Alternate renderers are not required for this RFC.

Risks

Cache safety

Incorrect cache scope or vary rules can leak user data.

Mitigations:

private-safe defaults
server-only config
strict mode
cache key inspection
private/public policy checks
dev warnings
manifest versioning
automatic fallback

Resumability correctness

Component render-symbol reuse and cached component output must preserve Qwik execution metadata.

Mitigations:

cache HTML with Qwik metadata
include manifest hash in keys
fallback on mismatch
validate serialized execution state
compare render-symbol output against normal SSR in tests

Static analysis limits

Some patterns will be too dynamic.

Mitigations:

prefer useAsync$(serverFn, props)
support normal callback form
warn when generation is skipped
keep opaque callbacks working
fallback to normal SSR

Desired MVP

The first version should support:

server$(fn)
component$(fn)
useAsync$(serverFn, props)
server-only global cache config
generated cache manifest defaults
server-only user config overrides
minimal async cache store adapter
server function graph metadata
component render-symbol registry metadata
cache eligibility lint warnings
request-level dedupe
server function cache
useAsync$ state envelope cache
rendered HTML cache
pending Suspense output for optimized components
normal Qwik SSR fallback
dev warnings for ineligible render symbols

The first version does not need:

Go-native execution
Rust-native execution
Zig-native execution
full portable JavaScript IR
GraphQL integration
public component render endpoints by default
render-symbol config in component$
client-fetched partial transport from CDN, edge, or origin
full CDN/edge adapter implementation for every platform

Future Direction: Client-Fetched Partials

The cache registry and component render registry should leave room for client-fetched partials later, but this is not the first RFC's main fast path.

The first RFC should focus on:

server$ resource registry
component render registry
useAsync$ graph edges
request fan-out and dedupe
server$ result caching
useAsync$ state-envelope caching
rendered component HTML caching
normal Qwik SSR fallback

Those pieces make a later partial system possible because Qwik will already know:

which server$ resource produces the data
which component render symbol can consume it
which Suspense boundary owns pending/resolved/rejected output
which cache key and vary rules protect the result
which metadata is needed for resumability

A later RFC could add this call stack:

request
-> serve cached route shell or pending Suspense boundary
-> emit opaque partial references
-> browser fetches missing partials from CDN, edge, or origin
-> server chooses partial payload mode by policy
-> partial returns rendered HTML or data + component render-symbol reference
-> browser replaces the pending boundary
-> Qwik resumes with normal metadata

Payload modes

Future partial payloads should support both rendered HTML and data plus component render-symbol references. The server owns the policy decision.

html:
  return rendered component or route HTML + Qwik metadata
  best for route transitions, server-rendered partials, and HTML-over-the-wire updates

data-plus-render-symbol:
  return server$ data envelope + component render-symbol reference
  best when the client already has or can download the render symbol
  and should render with SSR-seeded, fetched, or local data

auto:
  server chooses based on cache hit, route policy, client capability,
  component eligibility, authorization, and resumability metadata needs

This gives Qwik two cacheable outputs from the same registry:

rendered component/page HTML:
  cached for SSR, route transitions, and server-rendered partial responses

component render-symbol JS executor:
  cached or registered so the client can render with compatible data when policy allows

The HTML path is useful for SPA-like route transitions that still receive HTML from the server. This is similar in spirit to Turbo Drive's HTML-over-the-wire navigation: the browser can request a page, receive HTML, and update the current document without a full browser reload. Qwik should keep the analogy conceptual and preserve Qwik's own resumability metadata, component render registry, and cache policy model.

Permission-aware component fetching

Fetchable partials and fetchable component render symbols must be permission-aware.

public component:
  may be fetched or rendered when manifest and public cache policy allow it

private component:
  must verify route, session, customer, tenant, or permission state on the server
  before returning rendered HTML or data-plus-render-symbol payloads

shared component:
  public only when all props, server$ dependencies, vary rules, and cache policy
  are public-safe

Opaque IDs are not authorization. Admin panels, customer dashboards, and locked-down route components must not be fetchable, renderable, or downloadable just because the browser knows a component ID.

For private payloads:

rendered HTML cache keys must include the user/customer/tenant permission boundary
data-plus-render-symbol payloads must authorize both the server$ data and the component render symbol
client code cannot reinterpret public/private scope
server-only cache config is never exposed
authorization failure returns the normal route auth result or server-rendered fallback

That future design is useful for location-aware data:

public A/B or segment data:
  cacheable at CDN or edge when vary rules are explicit

private user-only data:
  fetched from origin or private cache
  never stored in public CDN cache

The important constraint remains the same as the main RFC: the browser must not receive the server cache config. It can receive opaque IDs and safe metadata, but server policy, stores, credentials, and public/private decisions stay server-only.

Open Questions

  • Should the global config live in src/cache.server.ts, qwik.cache.server.ts, the router adapter config, or another server-only entry?
  • Should components listed in global config default to private rendered-component caching, request-only caching, or metadata-only mode?
  • Should public rendered-component caching require route-level confirmation?
  • Should component render endpoints be disabled by default?
  • Should useAsync$(() => getProduct(...)) be auto-detected when simple?
  • Should the generated cache manifest be a separate artifact or a section of q-manifest?
  • Future: how should client-fetched partials be batched across many component instances?
  • Future: how should adapters map CDN, edge, and origin placement to real deployment platforms?
  • How should Qwik expose server timing, cache hits, cache misses, and fallback reasons in devtools?
  • How should cache key inspection work in devtools?
  • How should optional tag invalidation integrate with Qwik Router adapters and stores that do not support tags?
  • How should registered component render symbols preserve Qwik metadata for every supported render feature?

References

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