Skip to content

feat(website): native ?renderJson support in the Fresh handler - #1613

Open
marcoferreiradev wants to merge 4 commits into
deco-cx:mainfrom
oficina-dev:pr/render-json
Open

feat(website): native ?renderJson support in the Fresh handler#1613
marcoferreiradev wants to merge 4 commits into
deco-cx:mainfrom
oficina-dev:pr/render-json

Conversation

@marcoferreiradev

@marcoferreiradev marcoferreiradev commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What is this Contribution About?

Native ?renderJson support in the website Fresh handler — the consumer side of the section-controlled JSON rendering introduced in deco-cx/deco#1209.

  • website/handlers/fresh.ts: a native ?renderJson branch (sibling of ?asJson, with renderJson taking precedence, guarded by !isJsonOneShot). Serves the JSON projection produced by each section's renderJson export via the runtime's serializeResolvedSection / computeRenderCb.
  • website/mod.ts: a renderJson.sectionsToIgnore admin prop so a site can override which sections are dropped from the JSON payload, instead of a hardcoded denylist.
  • Drops the legacy ?appJson alias — ?renderJson is its replacement. The standalone ?asJson mode is left untouched.

Issue Link

Companion to the runtime feature — depends on it:

Status / dependency

Important

Draft — blocked on deco-cx/deco#1209. This PR imports computeRenderCb, serializeResolvedSection, ResolvedSection, sectionModuleLookup and SerializeContext from @deco/deco. Until #1209 is merged and published to JSR, deno task check fails with 5× TS2305: has no exported member ... for those symbols. deno fmt --check and deno lint are clean.

Test plan

  • deno fmt --check clean
  • deno check — blocked on Avoid loops on slack mcp #1209 (5× TS2305 for the not-yet-published runtime exports)
  • Dogfooded on a real deco storefront (oficina-reserva, pinned to the fork build): ?asJson and HTML byte-identical to baseline, ?renderJson matches the runtime serialization, lazy fetch applies the per-section projection.

Loom Video / Demonstration

Validated on the oficina-reserva storefront preview (pinned to the fork build). Loom/demo available on request.

🤖 Generated with Claude Code


Summary by cubic

Add native ?renderJson to the website Fresh handler to return a structured JSON view of pages, honoring each section’s renderJson export. Adds renderJson.sectionsToIgnore in app props and removes the ?appJson alias; ?asJson stays as-is.

  • New Features

    • website/handlers/fresh.ts: new ?renderJson path serializes sections via serializeResolvedSection and computeRenderCb from @deco/deco, returning { name, path, sections }.
    • Sections opted out of JSON are skipped before loaders run; site-owned sections use export const renderJson = false; app-owned sections can be ignored via renderJson.sectionsToIgnore (matched by resolveType suffix).
    • ?renderJson takes precedence over ?asJson; JSON responses bypass async render (first-byte threshold).
  • Bug Fixes

    • Hardened renderJson.sectionsToIgnore handling: filter to strings, trim, and drop empties to prevent TypeErrors and accidental full-page drops.

Written for commit 75cdee2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a new JSON rendering mode with configurable section filtering capabilities.
    • Users can now exclude specific sections from JSON responses via configuration.

marcoferreiradev and others added 2 commits June 18, 2026 11:37
fresh.ts gains a structured-JSON exit alongside the existing ?asJson one:
?renderJson (legacy alias ?appJson — remove once consumers switch) resolves
the page with hooks that short-circuit sections opted out of JSON rendering
(their loaders never run), serializes the tree via @deco/deco
serializeResolvedSection honoring each section's `renderJson` export, and
responds { name, path, sections } with lazy sections as
{ component, lazyUrl } placeholders. One-shot JSON responses never use
async render (firstByteThreshold guards extended); renderJson takes
precedence when both params are sent; legacy ?asJson is untouched.

The website app gains `renderJson.sectionsToIgnore` (admin-configurable):
app-owned sections excluded by resolveType suffix — site-owned sections
should prefer `export const renderJson = false` in their own file.

Replaces the site-level wrapper pattern (handler + pages-loader fork) that
oficina-reserva ran as v1 — the default website/loaders/pages.ts now works
unchanged for renderJson consumers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
renderJson is the replacement, not a sibling — consumers adopt ?renderJson
directly (validation happens on PR previews, nothing in production speaks
?appJson). Only the legacy ?asJson remains as a separate, untouched mode.

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

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Fresh handler gains a renderJson query parameter that acts as a higher-priority one-shot JSON mode. A new isJsonOneShot flag unifies gating of async-render paths. When renderJson is active, section modules are resolved to build a dropping predicate, resolver hooks stub dropped sections, and a new response branch serializes and returns a filtered { name, path, sections } JSON payload. The website/mod.ts Props interface gains a matching renderJson?: { sectionsToIgnore?: string[] } config.

Changes

renderJson one-shot JSON mode

Layer / File(s) Summary
Props.renderJson config and new imports
website/mod.ts, website/handlers/fresh.ts
Adds renderJson?: { sectionsToIgnore?: string[] } to Props, extends AppContext Pick to include renderJson, and imports computeRenderCb, serializeResolvedSection, sectionModuleLookup, SerializeContext, ResolvedSection, and isDeferred from @deco/deco.
isJsonOneShot detection and async-render gating
website/handlers/fresh.ts
Detects the renderJson query param and introduces isJsonOneShot (true when asJson or renderJson is set), then applies it to gate firstByteThreshold, abort propagation, first-byte instrumentation, and the async-render response stage.
Section dropping predicate and resolver hooks
website/handlers/fresh.ts
When renderJson is active, loads section modules via sectionModuleLookup(), builds isDroppedSection() from ignore suffixes and per-section renderJson === false, and wires onPropsResolveStart/onResolveStart hooks to stub dropped sections, preventing their inline loaders from running.
JSON serialization response branch
website/handlers/fresh.ts
After page resolution, builds a SerializeContext via computeRenderCb, serializes sections with serializeResolvedSection, filters out nulls and stubs, and returns Response.json({ name, path, sections }) with CORS headers, taking precedence over the existing asJson branch.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 A hop through the handler, a query appears,
renderJson mode now calms async fears.
Dropped sections are stubbed, their loaders stand still,
Serialized sections returned with a thrill.
The JSON response, crisp as morning dew —
One-shot, CORS-wrapped, and thoroughly new! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(website): native ?renderJson support in the Fresh handler' accurately and specifically describes the main feature being added — native renderJson query parameter support in the Fresh handler.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively covers the contribution, issue context, and implementation details, though the Loom Video and Demonstration Link sections reference external resources unavailable for verification.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.158.1 update
  • 🎉 for Minor 0.159.0 update
  • 🚀 for Major 1.0.0 update

@marcoferreiradev
marcoferreiradev marked this pull request as ready for review June 18, 2026 15:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/handlers/fresh.ts`:
- Around line 183-187: The sectionsToIgnore array can contain empty or blank
strings, which causes the endsWith check in the isDroppedSection function to
match all section names (since every string ends with an empty string),
inadvertently dropping entire pages. Modify the line where sectionsToIgnore is
assigned to filter and trim the array, removing any blank suffixes before the
array is used in the isDroppedSection condition. This ensures only valid
non-empty suffixes are checked when determining if a section should be dropped.
- Around line 293-299: The handler function in fresh.ts casts appContext as any
and accesses fields like revision, release, vary, and deco.ctx.deploymentId that
are not declared in the appContext parameter's Pick type. Update the Pick type
in the appContext parameter definition to explicitly include these fields:
revision, release, vary, and deco, in addition to the currently declared fields
(monitoring, response, caching, firstByteThresholdMS, isBot, flavor,
renderJson). After updating the Pick type to include all accessed fields, remove
the as any cast so the type system properly enforces that these dependencies are
available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 630726b5-5660-4c29-9008-34a93faeab66

📥 Commits

Reviewing files that changed from the base of the PR and between 192c63c and a84e9f5.

📒 Files selected for processing (2)
  • website/handlers/fresh.ts
  • website/mod.ts

Comment thread website/handlers/fresh.ts Outdated
Comment thread website/handlers/fresh.ts

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread website/handlers/fresh.ts Outdated
A blank entry in renderJson.sectionsToIgnore made resolveType.endsWith("")
match every section, so ?renderJson could drop an entire page. Trim each
configured suffix and discard empties before the isDroppedSection check.

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

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread website/handlers/fresh.ts Outdated
sectionsToIgnore comes from admin config (external JSON) and is not
type-guaranteed at runtime. The previous `.map(s => s.trim())` would throw
a TypeError (500) on a non-string entry — the old `endsWith` tolerated it
via coercion. Filter to strings before trimming, and keep only non-empty
suffixes (a blank entry would make endsWith("") drop the whole page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant