Skip to content

fix(web): never cache a degraded SSR render; guard the feed card's cover check - #1882

Merged
feruzm merged 9 commits into
developfrom
fix/ssr-degraded-no-cache-card-guard
Sep 24, 2026
Merged

feruzm merged 9 commits into
developfrom
fix/ssr-degraded-no-cache-card-guard

Conversation

@feruzm

@feruzm feruzm commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Two web SSR fixes, one or more commits per issue. Touches the production container command (apps/web/Dockerfile adds a second --require preload), so worth a look at the startup log after deploy.

Degraded SSR renders are never stored (#1558)

When a server prefetch times out or fails (RPC 5xx, every node exhausted), the page renders without that data and the client fetches it after hydration. The middleware picked the route's Cache-Control before the render started, so that degraded document kept its s-maxage and was stored at the origin and the edge for the whole tier: five minutes of an entry-less profile for every visitor, longer on post pages.

The App Router gives a server component no way to change a response header once rendering has begun, and throwing turns into a 500 that keeps the same Cache-Control. So this adds apps/web/ssr-degraded.js, a dependency-free preload next to ssr-admission.js:

  • each request runs in its own async context (verified against Next 15.5's in-process render and the production Flight renderer: a mark always lands on the request it ran under, never a concurrent one);
  • the SSR query helpers mark the request when a prefetch times out, when fetchQuery rejects, or when prefetchQuery leaves its query in error state (react-query swallows that error);
  • a node's not-found answer (missing post, account, tag or community, recognised by isHiveNotFoundError, shared with the RSS handler's Sentry filter) is a real answer and keeps its normal caching, so dead links stay cached;
  • a source the caller falls back from opts out per call: entry metadata and the oEmbed/agent loader read condenser get_content with prefetchQuery(..., { degradeOnFailure: false }) and then bridge.get_post with the default, so a condenser failure alone leaves a page the bridge resolved completely cacheable; nothing is ever unmarked, so one query's success cannot clear another's failure. The fallback is not lossless for replies (bridge omits root_*), so a bridge-served reply at depth 2+ whose thread root cannot be recovered (threadRoot, shared with canonicalTarget) is marked fallback-incomplete;
  • when the response head is written, a marked response goes out as private, no-store with -degraded appended to x-cache-tier, so neither nginx nor the edge stores it and an expired good copy stays available to be served stale; the status is untouched;
  • once a minute, only when non-zero, the container log gets one [ssr-degraded] line counting sent, late (head already flushed) and abandoned (client gone before any head, including a mark that arrives after the client left), each by reason with sample paths.

Known limit, tracked in #1881: a render that already flushed its head (RSC navigations, routes with a loading.tsx above their prefetch) cannot be changed and is still cached; those show up as late.

Tradeoff: during an upstream slowdown a URL with no cached copy is no longer shielded by a cached empty page. Stale serving (proxy_cache_use_stale updating, stale-while-revalidate) and the per-process admission cap still apply; a short s-maxage for degraded pages was rejected because it would replace a good stale copy with the empty one.

Closes #1558

Profile tabs: last unguarded reader on the card path (#1805)

The route work #1805 asks for (delete [section]/loading.tsx, structure and stream specs) already landed in #1811 (f517fdd), which did not reference the issue. This closes the one untrusted reader still unguarded on those tabs' card path: EntryListItemThumbnail called getEntryCardImageRawUrl(entry) raw, one line after the guarded catchPostImageSafely on the same entry and the same throw surface, so a body that broke one would rethrow from the other with no boundary above the cards. getEntryCardImageRawUrlSafely degrades a throw to null ("not animated", srcset kept) and reports once per post. No known input throws today; this is the same defence in depth as #1790 and #1814.

Please measure /@ecency/comments, /@ecency/replies and /@ecency/blog on production with the recipe in #1805 after deploy.

Closes #1805

Test plan

  • 440 spec files / 4227 tests in the touched areas pass on the combined branch (real preload in a child process: unmarked render untouched, timeout and error sent no-store with status kept, not-found left cacheable, concurrent renders isolated, sent/late/abandoned accounting, coexists with ssr-admission, wired in the Dockerfile); tsc --noEmit and eslint clean
  • Every guard mutation-checked; each issue had an adversarial review, and each fix round was re-reviewed
  • After deploy: the web containers start with both preloads; origin responses for a degraded render carry x-cache-tier: <tier>-degraded and cache-control: private, no-store; [ssr-degraded] lines appear in the web service log

Summary by CodeRabbit

  • Bug Fixes
    • Pages affected by server-side data-fetch failures or timeouts are no longer stored in the cache, helping prevent incomplete content from being served to later visitors.
    • Entry pages retain thread context more reliably when data comes from a fallback source.
    • Feed thumbnails remain visible when image details cannot be processed, and animated images continue to display correctly.
    • Missing posts and other not-found results are handled separately from temporary service errors.
  • Documentation
    • Added guidance on how incomplete page renders are handled by caching.

The card calls getEntryCardImageRawUrl on the same entry as its two
catchPostImageSafely calls, during an SSR render with no boundary above
it on the profile tabs, feed and community routes. A throw from it would
undo the thumbnail guard and fail the whole document. Degrade it to null
(treated as not animated) and report once.
A prefetch that outlives the SSR timeout resolves undefined and the page
renders without its data, but the middleware chose Cache-Control before
the render began, so the degraded document kept the route's s-maxage and
was stored at the origin and the edge for the whole tier.

A new dependency-free preload gives each request an async context. The
timeout marks the request it ran under and the response head is rewritten
to private, no-store with a -degraded x-cache-tier, so no layer stores it
and an expired good copy stays available to be served stale. Timeouts after
the head was flushed are counted as late, and both counts reach the
container log once a minute.
… caches

A prefetch that fails (RPC 5xx, every node exhausted) renders the same
entry-less page as one that times out and was still cached: prefetchQuery
swallows the error inside react-query, and withSsrTimeout's catch resolved
undefined without marking. Both now mark the response prefetch-error; the
page's status is untouched, so a notFound() after a failed lookup still
sends its 404, uncached.

The preload now counts each outcome by reason with its own sample paths:
sent (head written as no-store), late (head already flushed, still
cached, which names the streamed routes) and abandoned (client gone
before any head, so nothing was sent).
A missing post, account, tag or community comes back from a Hive node as
an assert error, but it is a real answer, not a degraded render. Marking
it no-store sent every dead-link and crawler hit on a missing post back
to the renderer. The not-found asserts the RSS handler already
recognised move into a shared isHiveNotFoundError (now also covering the
post and community shapes), and both prefetch-error marks skip it.
Transport, 5xx, rate-limit, timeout and node-exhaustion errors are still
marked.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 37 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: f4697297-677b-439e-9a64-50c6cbdfb733

📥 Commits

Reviewing files that changed from the base of the PR and between 9d34cd3 and 356e020.

📒 Files selected for processing (14)
  • apps/web/Dockerfile
  • apps/web/src/core/entries/catch-post-image-safely.ts
  • apps/web/src/core/entries/report-render-helper-failure.ts
  • apps/web/src/core/react-query/query-helpers.ts
  • apps/web/src/features/rss/rss-handler.ts
  • apps/web/src/features/shared/entry-list-item/entry-list-item-thumbnail.tsx
  • apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts
  • apps/web/src/specs/features/shared/catch-post-image-guarded-call-sites.spec.ts
  • apps/web/src/specs/features/shared/entry-list-item-hostile-body.spec.tsx
  • apps/web/src/specs/ssr-degraded.spec.ts
  • apps/web/src/specs/utils/hive-not-found-error.spec.ts
  • apps/web/src/utils/hive-not-found-error.ts
  • apps/web/ssr-degraded.js
  • docs/cache/README.md

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.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Prevent caching degraded SSR renders and guard feed cover checks

🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Prevents shared caches from storing SSR pages rendered after upstream prefetch failures.
• Preserves caching for genuine Hive not-found responses and records degraded outcomes.
• Guards feed-card cover detection so malformed entries cannot abort SSR.
Diagram

sequenceDiagram
  actor V as Visitor
  participant C as Shared Caches
  participant H as Node HTTP
  participant D as Degraded Guard
  participant Q as Query Helpers
  participant R as Hive RPC
  V->>C: Request page
  C->>H: Cache miss
  H->>D: Open request context
  D->>Q: Run SSR render
  Q->>R: Prefetch data
  alt Success or not found
    R-->>Q: Valid result
    Q-->>D: Complete render
    D-->>C: Normal cache policy
  else Timeout or failure
    Q->>D: Mark degraded
    D-->>C: Private no-store
  end
  C-->>V: Page response
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Propagate failures through routes
  • ➕ Avoids patching Node HTTP response methods.
  • ➕ Makes degraded state explicit in route-level control flow.
  • ➖ App Router cannot revise response headers after rendering begins.
  • ➖ Requires broad route changes and may turn graceful fallbacks into errors.
  • ➖ Streaming can flush headers before route logic handles the failure.
2. Use a short degraded cache lifetime
  • ➕ Simpler than request-local response interception.
  • ➕ Provides temporary shielding during prolonged upstream outages.
  • ➖ Still serves incomplete pages to multiple visitors.
  • ➖ Can replace a valid stale entry with a degraded document.
  • ➖ Does not preserve the strongest stale-serving behavior.
3. Introduce a custom Next server
  • ➕ Provides an explicit HTTP lifecycle integration point.
  • ➕ Avoids globally replacing Server.prototype.emit from a preload.
  • ➖ Adds substantial deployment and maintenance complexity.
  • ➖ Duplicates behavior handled by the standard next start server.
  • ➖ May require ongoing adaptation to internal Next.js changes.

Recommendation: The preload is the best practical option under the current App Router constraints: it centralizes degraded-render handling, preserves good stale cache entries, retains response statuses, and requires no route-by-route changes. Keep the child-process concurrency and preload-order tests as upgrade guards because the solution relies on Node and Next.js request lifecycle behavior.

Files changed (14) +759 / -22

Bug fix (5) +196 / -8
catch-post-image-safely.tsAdd a safe raw cover URL wrapper +22/-1

Add a safe raw cover URL wrapper

• Wraps 'getEntryCardImageRawUrl' so extraction failures are reported once and degrade to 'null' instead of aborting rendering.

apps/web/src/core/entries/catch-post-image-safely.ts

report-render-helper-failure.tsRecognize raw cover extraction failures +1/-1

Recognize raw cover extraction failures

• Extends the typed render-helper call-site set with 'getEntryCardImageRawUrl', enabling consistent Sentry grouping and per-post deduplication.

apps/web/src/core/entries/report-render-helper-failure.ts

query-helpers.tsMark failed SSR prefetches as degraded +46/-3

Mark failed SSR prefetches as degraded

• Marks request-local degradation on SSR timeouts, rejected fetches, and React Query error states. Genuine Hive not-found responses remain cacheable, while client and non-preloaded environments retain graceful fallback behavior.

apps/web/src/core/react-query/query-helpers.ts

entry-list-item-thumbnail.tsxGuard feed-card animated cover detection +7/-3

Guard feed-card animated cover detection

• Routes the card's raw cover lookup through the safe wrapper. Extraction failures now treat the cover as non-animated and preserve the thumbnail and srcset.

apps/web/src/features/shared/entry-list-item/entry-list-item-thumbnail.tsx

ssr-degraded.jsPrevent degraded SSR responses from entering shared caches +120/-0

Prevent degraded SSR responses from entering shared caches

• Adds a dependency-free preload using AsyncLocalStorage to isolate degradation marks by request and rewrite unsent response headers to 'private, no-store'. It preserves statuses and records sent, late, and abandoned outcomes by reason with sampled paths and periodic logs.

apps/web/ssr-degraded.js

Refactor (2) +25 / -7
rss-handler.tsReuse shared Hive not-found classification +5/-7

Reuse shared Hive not-found classification

• Replaces duplicated RSS error-message checks with the shared Hive not-found classifier while retaining transient upstream filtering behavior.

apps/web/src/features/rss/rss-handler.ts

hive-not-found-error.tsCentralize Hive not-found detection +20/-0

Centralize Hive not-found detection

• Introduces a shared classifier for Hive JSON-RPC assertions representing genuinely absent resources rather than upstream degradation.

apps/web/src/utils/hive-not-found-error.ts

Tests (5) +508 / -6
query-helpers-ssr-timeout.spec.tsTest SSR prefetch degradation marking +140/-0

Test SSR prefetch degradation marking

• Covers timeout, cancellation, rejected fetches, swallowed prefetch errors, infinite queries, successful empty results, not-found exceptions, and operation without the preload.

apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts

catch-post-image-guarded-call-sites.spec.tsEnforce guarded raw cover call sites +40/-4

Enforce guarded raw cover call sites

• Generalizes raw-import detection and verifies the feed card uses the safe cover helper. It also prevents other application files from importing the unguarded export.

apps/web/src/specs/features/shared/catch-post-image-guarded-call-sites.spec.ts

entry-list-item-hostile-body.spec.tsxTest feed cards against cover-check exceptions +51/-2

Test feed cards against cover-check exceptions

• Simulates a throwing raw cover extractor and verifies the card still renders its image and srcset. It also confirms Sentry reporting is deduplicated across rerenders.

apps/web/src/specs/features/shared/entry-list-item-hostile-body.spec.tsx

ssr-degraded.spec.tsIntegration-test the production SSR preload +238/-0

Integration-test the production SSR preload

• Boots the real preload in child processes and validates header rewriting, status preservation, request isolation, late and abandoned accounting, logging, admission-control coexistence, and Docker wiring.

apps/web/src/specs/ssr-degraded.spec.ts

hive-not-found-error.spec.tsTest Hive not-found error classification +39/-0

Test Hive not-found error classification

• Verifies known missing post, account, tag, category, and community messages are recognized without misclassifying transport failures or non-error values.

apps/web/src/specs/utils/hive-not-found-error.spec.ts

Documentation (1) +25 / -0
README.mdDocument degraded SSR cache behavior +25/-0

Document degraded SSR cache behavior

• Explains degradation classification, no-store header rewriting, stale-copy preservation, streaming limitations, outcome counters, and production log format.

docs/cache/README.md

Other (1) +5 / -1
DockerfileLoad the degraded-render guard in production +5/-1

Load the degraded-render guard in production

• Copies 'ssr-degraded.js' into the runtime image and adds it as a second application preload after SSR admission control. Comments document why degraded SSR responses must bypass shared caches.

apps/web/Dockerfile

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 356e020481

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/ssr-degraded.js
Comment on lines +114 to +115
res.once("close", () => {
if (ctx.reason && !res.headersSent) record("abandoned", ctx.reason, req);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Record disconnects that occur before the degraded mark

When a client disconnects while a prefetch is still pending, the close event runs while ctx.reason is null, so nothing is recorded; if the prefetch later reaches its 10-second timeout, mark() sets the reason after this one-shot listener has already fired and the request never appears in abandoned. This is especially likely for the async render tails this metric is intended to expose, and causes both the lifetime totals and minute logs to underreport them; retain a closed flag and record when a later mark arrives.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 51212bd: close now sets ctx.closed, and a mark that arrives afterwards with no head ever written records abandoned, once. Specced with the real preload: the client leaves at 50ms, the mark arrives at 150ms, and the result is abandoned: 1 with nothing in sent.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Query tests bypass type checking 📘 Rule violation ⚙ Maintainability
Description
query-helpers-ssr-timeout.spec.ts declares client as any and adds as any assertions for both
getQueryClient and the infinite-query options. These escape hatches cover the shared fixture used
by every new timeout and error-path test, so type-incompatible mock changes are not checked by
TypeScript.
Code

apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[21]

+  let client: any;
Evidence
Compliance rule 2668119 prohibits every new use of any in TypeScript, including declarations and
assertions. The new specification declares its central mock as any and uses two additional `as
any` assertions.

Rule 2668119: Disallow implicit and any types in new TypeScript code
apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[17-34]
apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new query-helper tests use explicit `any` types for the shared client fixture, the mocked query-client accessor, and infinite-query options, bypassing compile-time validation.
## Fix Focus Areas
- apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[17-34]
- apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[81-90]
## Recommended Fix
Define a concrete mock-client type from the required `QueryClient` methods, use `vi.mocked(getQueryClient)` rather than asserting it as `any`, and construct correctly typed infinite-query options without an `as any` assertion.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Provider regressions escape query tests 📘 Rule violation ▣ Testability
Description
query-helpers-ssr-timeout.spec.ts replaces the internal getQueryClient export with vi.fn()
instead of mocking only the external boundary used by that module. Every timeout and failure case
therefore runs against a fabricated application helper, so later integration changes to the real
query-client provider can pass this suite unnoticed.
Code

apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[R5-7]

+vi.mock("../../../core/react-query/index", () => ({
+  getQueryClient: vi.fn()
+}));
Evidence
Compliance rule 2668008 prohibits replacing exports from internal application modules with vi.fn()
in unit tests. The new specification mocks the relative internal react-query module and substitutes
its getQueryClient export with a Vitest function.

Rule 2668008: Mock only external package dependencies with vi.fn in unit tests
apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[5-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The query-helper specification replaces the internal query-client provider with `vi.fn()`, contrary to the requirement that unit tests mock external dependencies rather than internal application modules.
## Fix Focus Areas
- apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[5-15]
- apps/web/src/specs/core/react-query/query-helpers-ssr-timeout.spec.ts[23-35]
## Recommended Fix
Use the real internal query-client provider with an isolated `QueryClient`, controlling only its external query functions or network boundary. Keep timer and degradation assertions against the public query-helper functions without replacing `getQueryClient` itself.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Successful fallbacks bypass shared caches ✓ Resolved 🐞 Bug ➹ Performance
Description
prefetchQuery calls markIfPrefetchFailed immediately for the preferred query, even when
loadEntry or metadata generation subsequently resolves the complete entry through its bridge
fallback. When the condenser source fails but the bridge succeeds, entry HTML and cacheable oEmbed
responses are still rewritten to private, no-store, increasing origin load during a partial
upstream outage.
Code

apps/web/src/core/react-query/query-helpers.ts[R91-92]

+  if (state?.status === "error" && !isHiveNotFoundError(state.error)) {
+    markSsrDegraded("prefetch-error");
Evidence
The helper marks every failed prefetch before returning, while both entry loaders explicitly retry
through a different query and can produce a complete result. The oEmbed route uses that loader and
intentionally emits a publicly cacheable response, which the preload then overrides solely because
the discarded first attempt failed.

apps/web/src/core/react-query/query-helpers.ts[88-93]
apps/web/src/core/react-query/query-helpers.ts[112-116]
apps/web/src/app/(dynamicPages)/entry/_helpers/agent-readable.ts[167-192]
apps/web/src/app/(dynamicPages)/entry/_helpers/generate-entry-metadata.ts[35-49]
apps/web/src/app/api/oembed/route.ts[6-16]
apps/web/src/app/api/oembed/route.ts[45-57]
apps/web/ssr-degraded.js[105-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The query helper marks the current response degraded as soon as one prefetch fails, even when the caller successfully obtains complete data from a fallback source. This causes complete entry pages and oEmbed responses to lose their intended shared caching.
## Fix Focus Areas
- apps/web/src/core/react-query/query-helpers.ts[51-94]
- apps/web/src/app/(dynamicPages)/entry/_helpers/agent-readable.ts[167-192]
- apps/web/src/app/(dynamicPages)/entry/_helpers/generate-entry-metadata.ts[35-49]
## Recommended Fix
Add a way for fallback probes to receive timeout/error outcome information without immediately marking the response. Update the entry-loading and metadata fallback chains to mark the request only when the overall chain leaves the rendered response without the required entry data; preserve normal caching when the bridge fallback succeeds.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can choose which labels appear on a finding, and whether they show icons or text

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/core/react-query/query-helpers.ts
The entry metadata and the oEmbed/agent loader read condenser get_content
first and fall back to bridge.get_post. A condenser failure alone marked
the response no-store even when bridge then returned the post, so a
complete page went out uncacheable during a partial outage. prefetchQuery
takes degradeOnFailure (default true); the preferred source opts out and
the fallback's own prefetch marks the response if it fails too. The
opt-out is scoped to that one call, so it never clears another query's
mark.

The preload also counts a client that left while a prefetch was still
pending: close now sets a flag, and a mark that arrives afterwards is
recorded as abandoned once.
bridge.get_post omits root_author/root_permlink, so a reply at depth 2 or
more served from the condenser fallback renders with no canonical, is
noindexed, and the discussion route answers its subtree. With the
condenser source opted out of marking, that render was cached for the
entry tier. Both entry loaders now mark it fallback-incomplete when the
served entry has no recoverable root.

The root rule moves out of canonicalTarget into an exported threadRoot
(a post is its own root, root_* when present, a depth-1 reply's parent),
which canonicalTarget and the loaders share. markSsrDegraded is exported
from the query helpers for this caller.
@feruzm
feruzm merged commit 5470f4e into develop Sep 24, 2026
7 of 8 checks passed
@feruzm
feruzm deleted the fix/ssr-degraded-no-cache-card-guard branch September 24, 2026 10:56
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.

Profile tab routes still stream their cards behind a boundary A timed-out SSR prefetch caches an entry-less page for five minutes

1 participant