Skip to content

fix(docs): keep notFound inside route boundaries#851

Open
Varkoff wants to merge 4 commits intoTanStack:mainfrom
Varkoff:fix/docs-notfound-server-boundary
Open

fix(docs): keep notFound inside route boundaries#851
Varkoff wants to merge 4 commits intoTanStack:mainfrom
Varkoff:fix/docs-notfound-server-boundary

Conversation

@Varkoff
Copy link
Copy Markdown

@Varkoff Varkoff commented Apr 22, 2026

Summary

Opening the official docs page for structured outputs currently breaks:

The underlying problem is that the shared docs helpers and createServerFn handlers were throwing notFound() directly. Once that crossed the server function boundary, SSR blew up before the route loader could do its normal redirect or 404 handling.

This patch keeps notFound() inside route boundaries:

  • shared docs helpers and docs server functions now throw a small docs-specific not-found error
  • route loaders treat that error the same way they already treat route-level not-found cases
  • the markdown route handlers translate that docs-specific error back into notFound() at the route layer

That keeps the redirect and 404 behavior in the route layer, and it stops the server function layer from leaking router notFound() across the boundary.

Screenshot

Structured outputs docs error

Test plan

  • pnpm run test:tsc
  • pnpm run test:lint
  • pnpm run test:smoke

Summary by CodeRabbit

  • Refactor
    • Unified missing-doc detection so all doc handlers recognize the same “docs not found” condition.
  • Bug Fixes
    • Doc requests that fail this check now try to resolve a redirect (308) and otherwise return a not-found response.
  • New Features
    • Added a dedicated docs-not-found error type and guard to standardize handling across routes and helpers.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 22, 2026

Deploy Preview for tanstack ready!

Name Link
🔨 Latest commit 9640c25
🔍 Latest deploy log https://app.netlify.com/projects/tanstack/deploys/69e9084f23c78700083c82cd
😎 Deploy Preview https://deploy-preview-851--tanstack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 31 (🔴 down 13 from production)
Accessibility: 90 (no change from production)
Best Practices: 83 (🔴 down 9 from production)
SEO: 97 (no change from production)
PWA: 70 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8ba2c8d-4d92-422c-a303-51615f1edbe9

📥 Commits

Reviewing files that changed from the base of the PR and between 9640c25 and c692ce4.

📒 Files selected for processing (1)
  • src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx

📝 Walkthrough

Walkthrough

Introduces a DocsNotFoundError type and uses it across docs utilities; loaders and server GET handlers now catch this error to attempt resolving a docs redirect (308) or call notFound(), rethrowing other errors unchanged.

Changes

Cohort / File(s) Summary
New error utilities
src/utils/docs-errors.ts
Add DocsNotFoundError type, createDocsNotFoundError() constructor, and isDocsNotFoundError() type guard.
Docs utilities
src/utils/docs.functions.ts, src/utils/docs.ts
Replace throws of notFound() with createDocsNotFoundError() for missing inputs / absent repo content; remove direct notFound dependency where applicable.
Route loader error handling
src/routes/$libraryId/$version.docs.$.tsx, src/routes/$libraryId/$version.docs.framework.$framework.$.tsx
Catch blocks extended to treat isDocsNotFoundError(error) as a docs-not-found signal alongside existing checks; preserved redirect-resolution and fallback-to-notFound() control flow.
Server GET route handlers
src/routes/...{$}[.]md.tsx, src/routes/...framework...{$}[.]md.tsx
Wrap loadDocs in try/catch; on isDocsNotFoundError attempt resolveDocsRedirect(...) — if found throw a 308 redirect to the resolved .md path, otherwise throw notFound(); other errors rethrown.
Imports & small flow edits
various src/routes/..., src/utils/...
Add/import isDocsNotFoundError, createDocsNotFoundError, and redirect/notFound where needed; adjust control flow to use the new error type.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through files both near and far,
Found a tiny flag that says "not here" on par.
I nudge the lost pages toward a new road,
Or softly say the doc's offload.
Hooray — redirects bloom where once was mar.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: preventing notFound() from being called across server function boundaries by keeping it within route handlers.
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.

✏️ 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/utils/docs-errors.ts (1)

1-4: Consider exporting DocsNotFoundError.

The type is only used internally now, but callers that want to type a caught error (e.g., in tests or future route handlers) currently have no public name for it. Minor — happy to skip if intentional.

♻️ Proposed change
-type DocsNotFoundError = Error & {
+export type DocsNotFoundError = Error & {
   data: { message: string }
   isDocsNotFound: true
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/docs-errors.ts` around lines 1 - 4, The type DocsNotFoundError is
currently declared but not exported; make it a public type by exporting it
(e.g., change the declaration to export type DocsNotFoundError = ...), so
callers/tests can import and use it when typing caught errors; ensure any
modules that need it import { DocsNotFoundError } from this module.
src/routes/$libraryId/$version.docs.{$}[.]md.tsx (1)

27-42: Consider attempting a redirect before returning 404 for .md endpoints.

The HTML routes ($version.docs.$.tsx, $version.docs.framework.$framework.$.tsx) call resolveDocsRedirect on not-found and issue a 308 if a redirect is configured. This .md endpoint now just returns notFound(), which means tools/LLMs/curl hitting a renamed .md URL will see a 404 even though the HTML URL would 308 to the new location. Minor consistency gap — worth aligning if feasible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`$libraryId/$version.docs.{$}[.]md.tsx around lines 27 - 42, When
loadDocs throws and isDocsNotFoundError(error) is true, attempt the same
redirect logic used by the HTML routes: call resolveDocsRedirect(...) with the
same repo, branch, docsRoot (root) and docsPath and if it returns a redirect
target throw a 308 redirect to that URL (e.g. redirect(308, target)), otherwise
fall back to throw notFound(); update the catch block around loadDocs to perform
this check before throwing notFound so renamed .md URLs mirror the HTML
endpoints' redirect behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/docs-errors.ts`:
- Around line 1-25: The route error guards fail to detect serialized
DocsNotFound errors because custom Error properties are lost during TanStack
Start serialization; update the error handling in the route handlers that
currently use isDocsNotFound/isDocsNotFoundError to also check for the
serialized shape by adding the structural fallback (error && typeof error ===
'object' && 'isDocsNotFound' in error) alongside the existing checks so that
createDocsNotFoundError() thrown from
fetchDocs/fetchFile/fetchRepoDirectoryContents will be recognized and trigger
the redirect/notFound() logic.

---

Nitpick comments:
In `@src/routes/`$libraryId/$version.docs.{$}[.]md.tsx:
- Around line 27-42: When loadDocs throws and isDocsNotFoundError(error) is
true, attempt the same redirect logic used by the HTML routes: call
resolveDocsRedirect(...) with the same repo, branch, docsRoot (root) and
docsPath and if it returns a redirect target throw a 308 redirect to that URL
(e.g. redirect(308, target)), otherwise fall back to throw notFound(); update
the catch block around loadDocs to perform this check before throwing notFound
so renamed .md URLs mirror the HTML endpoints' redirect behavior.

In `@src/utils/docs-errors.ts`:
- Around line 1-4: The type DocsNotFoundError is currently declared but not
exported; make it a public type by exporting it (e.g., change the declaration to
export type DocsNotFoundError = ...), so callers/tests can import and use it
when typing caught errors; ensure any modules that need it import {
DocsNotFoundError } from this module.
🪄 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: ea289840-608c-4a40-9266-7ecce00e513e

📥 Commits

Reviewing files that changed from the base of the PR and between 43c791f and d3c6a32.

⛔ Files ignored due to path filters (1)
  • .github/pr-assets/ai-guides-structured-outputs-error.png is excluded by !**/*.png
📒 Files selected for processing (7)
  • src/routes/$libraryId/$version.docs.$.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.$.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.{$}[.]md.tsx
  • src/routes/$libraryId/$version.docs.{$}[.]md.tsx
  • src/utils/docs-errors.ts
  • src/utils/docs.functions.ts
  • src/utils/docs.ts

Comment thread src/utils/docs-errors.ts
Comment on lines +1 to +25
type DocsNotFoundError = Error & {
data: { message: string }
isDocsNotFound: true
}

export function createDocsNotFoundError(message = 'No doc was found here!') {
const error = new Error(message) as DocsNotFoundError

error.name = 'DocsNotFoundError'
error.data = { message }
error.isDocsNotFound = true

return error
}

export function isDocsNotFoundError(
error: unknown,
): error is DocsNotFoundError {
return (
typeof error === 'object' &&
error !== null &&
'isDocsNotFound' in error &&
error.isDocsNotFound === true
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

TanStack Start createServerFn thrown error serialization custom properties preserved client

💡 Result:

In TanStack Start, errors thrown inside a createServerFn handler are sent back to the client only after being serialized (TanStack uses JSON/seroval-style serialization). This means any non-JSON-serializable data on the thrown value (e.g., Error objects via JSON.stringify, or custom properties that don’t serialize) cannot be preserved as-is on the client. 1) Why “serialization/custom properties preserved client” fails - TanStack explicitly serializes the error to return it to the client. The server-function handler catches the thrown value and JSON.stringify’s the serialized form of the error before returning it in the response body. - As a result, custom properties that aren’t representable by the serializer (and plain Error instances) will be lost or break serialization instead of being preserved. 2) What you can do - Throw Error instances with only JSON-compatible fields (message, name), or construct a plain object error payload yourself and throw/return something that is known-serializable. - Avoid returning/throwing types that include unknown or other non-serializable values; TanStack validates serializability and will error if the return type includes fields like unknown (example shown in a createServerFn issue). - If you need to keep extra error fields, ensure they are serializable primitives/objects (and not instances with methods/prototypes like Error, Response, etc.). 3) Source-backed notes from TanStack - TanStack documents that “Errors are serialized to the client” for server functions. - TanStack Start issue reports that “server functions can’t serialize error objects” because JSON.stringify can’t serialize Error objects (originally). A later comment indicates it was likely fixed, but the core constraint remains: serialization rules apply. - The server runtime code shows the catch path JSON.stringify’ing a cross-JSON serialization of the thrown error. If you paste your exact error shape (what you throw, e.g., throw new Error(...) vs throw {message, ...custom} vs throw someObject) and your expected client-side access (e.g., error.customProp), I can tell you the safest way to structure it for TanStack Start’s serializer.

Citations:


🏁 Script executed:

# First, find the files mentioned in the review comment
fd -t f "docs.functions.ts" 

# Also find the route files mentioned
fd -t f "$libraryId" --extension tsx

# Find test files that might test error handling
fd -t f -e test.ts -e test.tsx -e spec.ts -e spec.tsx

Repository: TanStack/tanstack.com

Length of output: 15260


🏁 Script executed:

# Search for usage of isDocsNotFoundError and how errors are handled
rg -t ts -t tsx "isDocsNotFoundError" -A 3 -B 3

Repository: TanStack/tanstack.com

Length of output: 92


🏁 Script executed:

# Search for the isNotFound pattern mentioned as existing fallback
rg -t ts -t tsx "isNotFound" -A 2 -B 2 | head -50

Repository: TanStack/tanstack.com

Length of output: 92


🏁 Script executed:

# Check if there's any serialization/error configuration in the codebase
rg -t ts -t tsx "seroval|Serializer|createServerFn" -l | head -20

Repository: TanStack/tanstack.com

Length of output: 92


🏁 Script executed:

# Read the docs-errors.ts file
cat -n src/utils/docs-errors.ts

Repository: TanStack/tanstack.com

Length of output: 800


🏁 Script executed:

# Read docs.functions.ts to see how errors are thrown
cat -n src/utils/docs.functions.ts

Repository: TanStack/tanstack.com

Length of output: 11462


🏁 Script executed:

# Check the route file for error handling patterns
cat -n src/routes/'$libraryId'/'$version'.docs.'$'.tsx | head -100

Repository: TanStack/tanstack.com

Length of output: 3477


🏁 Script executed:

# Look for isNotFound usage in route files
rg "isNotFound" src/routes --type-add 'tsx:*.tsx' -t tsx -A 3 -B 3 | head -80

Repository: TanStack/tanstack.com

Length of output: 4512


🏁 Script executed:

# Look for test files that might test the error serialization
fd -type f "test\|spec" | grep -i "docs\|error" | head -20

Repository: TanStack/tanstack.com

Length of output: 235


🏁 Script executed:

# Search for any test files testing the error behavior
rg "isDocsNotFoundError|createDocsNotFoundError" -t ts -t tsx -A 5 -B 5 | head -100

Repository: TanStack/tanstack.com

Length of output: 92


🏁 Script executed:

# Check if there's a serialization configuration in the project
rg "serialize|serializer" src/ --type-add 'tsx:*.tsx' -l | head -10

Repository: TanStack/tanstack.com

Length of output: 167


🏁 Script executed:

# Look at the other route file mentioned to see its error handling
sed -n '1,100p' 'src/routes/$libraryId/$version.docs.framework.$framework.$.tsx'

Repository: TanStack/tanstack.com

Length of output: 3207


Add fallback structural check for serialized isDocsNotFound property.

TanStack Start serializes errors across the server-function boundary, and custom Error properties don't survive this serialization. The route handlers in src/routes/$libraryId/$version.docs.$.tsx and .framework.$framework.$.tsx already handle this for Router errors with the fallback 'isNotFound' in error check (lines 42 and 45), but lack an equivalent fallback for isDocsNotFound.

Update the error handling in both routes to include:

(error && typeof error === 'object' && 'isDocsNotFound' in error)

alongside the existing structural checks. This ensures that createDocsNotFoundError() thrown from fetchDocs, fetchFile, and fetchRepoDirectoryContents will be properly detected on the client after serialization, and the redirect/notFound() fallback logic will trigger correctly for client-side navigation into non-existent docs pages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/docs-errors.ts` around lines 1 - 25, The route error guards fail to
detect serialized DocsNotFound errors because custom Error properties are lost
during TanStack Start serialization; update the error handling in the route
handlers that currently use isDocsNotFound/isDocsNotFoundError to also check for
the serialized shape by adding the structural fallback (error && typeof error
=== 'object' && 'isDocsNotFound' in error) alongside the existing checks so that
createDocsNotFoundError() thrown from
fetchDocs/fetchFile/fetchRepoDirectoryContents will be recognized and trigger
the redirect/notFound() logic.

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