Skip to content

add redirects into frontmatter & update deps#797

Merged
tannerlinsley merged 15 commits intomainfrom
redirect-frontmatter
Mar 30, 2026
Merged

add redirects into frontmatter & update deps#797
tannerlinsley merged 15 commits intomainfrom
redirect-frontmatter

Conversation

@LadyBluenotes
Copy link
Copy Markdown
Member

@LadyBluenotes LadyBluenotes commented Mar 30, 2026

Summary by CodeRabbit

  • New Features

    • Site-wide redirect support: posts can declare redirect_from aliases; docs and blog routes resolve variants (including framework-qualified) and issue permanent (308) redirects.
    • Cached redirect manifest and resolution endpoint to speed redirect lookups.
  • Documentation

    • Added maintainer guide covering docs markdown, redirect workflow, tabs, framework blocks, and avoiding duplicate redirects.
  • Bug Fixes / Accessibility

    • Improved embed iframe titles and restored visible markdown link accessibility.
  • Chores

    • Updated tooling, dependencies, and bumped Node engine requirement.

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 30, 2026

Deploy Preview for tanstack ready!

Name Link
🔨 Latest commit 6e3c431
🔍 Latest deploy log https://app.netlify.com/projects/tanstack/deploys/69cae5b669b1a30008ef81a3
😎 Deploy Preview https://deploy-preview-797--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: 26 (🔴 down 29 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 Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Route loaders and utilities now resolve frontmatter redirect_from aliases for docs and blog posts using a cached redirect manifest; matching requests are short-circuited with HTTP 308 redirects to canonical paths before loading content.

Changes

Cohort / File(s) Summary
Docs redirect runtime
src/utils/docs.ts
Added resolveDocsRedirect and fetchDocsRedirectManifest (server fn). Scans repo docs, builds/caches a redirect manifest from frontmatter redirect_from, normalizes candidates, and returns the first matching target or null.
Redirect manifest builder
src/utils/redirects.ts
New RedirectManifestEntry type and buildRedirectManifest(entries, opts) that returns a from→to mapping, supports target formatting, and throws on conflicting mappings with source context.
Frontmatter normalization
src/utils/documents.server.ts
Added normalizeRedirectFrom(value: unknown) and updated extractFrontMatter to include normalized redirect_from / redirectFrom arrays in returned data.
Content collection schema
content-collections.ts
Posts collection accepts optional redirect_from: string[]; transform uses normalizeRedirectFrom and emits both redirect_from and redirectFrom fields in outputs.
Docs route loaders
src/routes/$libraryId/$version.docs.$.tsx, src/routes/$libraryId/$version.docs.framework.$framework.$.tsx
Loaders precompute branch/docsRoot, call resolveDocsRedirect with candidate docsPaths, and immediately throw a 308 redirect to normalized /.../docs/<resolved> when matched; loadDocs args updated to use computed branch/docsRoot.
Blog route handler
src/routes/blog.$.tsx
Builds redirect map from allPosts[*].redirectFrom, normalizes incoming paths (with/without /blog/), and issues 308 redirects to matching post slugs before other handlers.
Docs manifest helpers
src/utils/...
Helpers added to flatten repo docs tree, compute canonical docs paths, normalize redirect paths with docsRoot awareness, and filter self-redirects.
Docs UI hooks
src/components/DocsLayout.tsx, src/components/Doc.tsx
Added useOptionalWidthToggle hook and switched Doc to use it (no exception-based fallback); minor iframe title and aria adjustments in embed/component.
Documentation
docs-info.md
New maintainer guide detailing docs markdown features, redirect workflow using redirect_from, tabs/framework blocks, supported Markdown features, and editing notes.
Tooling & metadata
package.json, .oxlintrc.json, eslint.config.mjs
Switched linting to oxlint with new .oxlintrc.json, removed eslint.config.mjs contents, bumped/removed many deps, raised engines.node to >=20.0.0.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Route as Docs Route Loader
    participant Resolver as resolveDocsRedirect
    participant Manifest as fetchDocsRedirectManifest
    participant Repo as Repo Files / Frontmatter
    participant Docs as loadDocs

    Client->>Route: GET /<lib>/<ver>/docs/old-page
    Route->>Resolver: resolveDocsRedirect({repo,branch,docsRoot,docsPaths})
    Resolver->>Manifest: fetchDocsRedirectManifest(repo,branch,docsRoot)
    Manifest->>Repo: read docs tree & frontmatter
    Repo-->>Manifest: return redirect entries
    Manifest-->>Resolver: cached manifest
    Resolver-->>Route: resolved target path or null
    rect rgba(100,150,255,0.5)
        Resolver-->>Route: returns /docs/new-page
        Route->>Client: 308 Redirect to /<lib>/<ver>/docs/new-page
    end
    alt No redirect
        Route->>Docs: loadDocs(branch, docsRoot, path)
        Docs-->>Route: page content
        Route-->>Client: 200 Rendered page
    end
Loading
sequenceDiagram
    actor Client
    participant Route as Blog Route
    participant Posts as Posts Collection
    participant Mapper as Redirect Map

    Client->>Route: GET /blog/old-slug
    Route->>Posts: fetch allPosts (includes redirectFrom)
    Posts->>Mapper: build/return redirect map (old → target slug)
    Route->>Mapper: lookup normalized request path
    rect rgba(100,150,255,0.5)
        Mapper-->>Route: returns /blog/new-slug
        Route->>Client: 308 Redirect to /blog/new-slug
    end
    alt No match
        Route->>Client: continue to other handlers / 404
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through folders, sniffed each name,
Found old paths hidden, nudged them to the same.
Built a gentle map from frontmatter's trail,
Sent browsers bouncing with a tidy 308 sail.
Hop on, dear reader — your link won't fail!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two main objectives: adding redirects support via frontmatter and updating dependencies, both of which are comprehensively addressed in the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch redirect-frontmatter

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/routes/blog.$.tsx (1)

26-54: Consider caching the redirect manifest.

The redirect manifest is rebuilt on every request to handleRedirects. Since allPosts is a static import whose data doesn't change at runtime, the manifest could be computed once at module load time.

♻️ Proposed optimization
+// Compute redirect manifest once at module load
+const blogRedirectManifest = buildRedirectManifest(
+  allPosts.flatMap((post) =>
+    (post.redirectFrom ?? []).map((redirectFrom) => ({
+      from: normalizeBlogRedirectPath(redirectFrom),
+      to: post.slug,
+      source: post._meta.filePath,
+    })),
+  ),
+  {
+    label: 'blog posts',
+    formatTarget: (target) => `/blog/${target}`,
+  },
+)
+
 function handleRedirects(docsPath: string) {
-  const redirectEntries = allPosts.flatMap((post) =>
-    (post.redirectFrom ?? []).map((redirectFrom) => ({
-      from: normalizeBlogRedirectPath(redirectFrom),
-      to: post.slug,
-      source: post._meta.filePath,
-    })),
-  )
-  const redirectOwners = buildRedirectManifest(redirectEntries, {
-    label: 'blog posts',
-    formatTarget: (target) => `/blog/${target}`,
-  })
-
   const normalizedPaths = new Set([
     normalizeBlogRedirectPath(docsPath),
     normalizeBlogRedirectPath(`/blog/${docsPath}`),
   ])

   const redirectedPostSlug = Array.from(normalizedPaths).flatMap((path) => {
-    const redirectOwner = redirectOwners[path]
+    const redirectOwner = blogRedirectManifest[path]

     return redirectOwner ? [redirectOwner] : []
   })[0]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/blog`.$.tsx around lines 26 - 54, The redirect manifest is rebuilt
on each call to handleRedirects; move the build out of the function so it runs
once at module load: compute redirectEntries from allPosts and call
buildRedirectManifest to produce a cached redirectOwners (or similar top-level
constant) and have handleRedirects use that cached redirectOwners (keep
normalizeBlogRedirectPath, normalizedPaths, and the final redirect throw logic
intact so only the manifest construction is relocated).
src/utils/docs.ts (1)

177-209: Promise.all fetches all markdown files in parallel without concurrency limits.

When the redirect manifest cache is cold, Promise.all on line 177 fetches all markdown files concurrently. While fetchRemote uses raw.githubusercontent.com (which doesn't enforce rate limits like the GitHub API), this approach may cause unnecessary memory pressure with large doc sets. The 10-minute cache TTL should prevent this from being a frequent issue, but adding concurrency limits (e.g., with a library like p-limit) would provide cleaner resource management, especially during cold cache scenarios after server restarts.

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

In `@src/utils/docs.ts` around lines 177 - 209, The current Promise.all over
markdownFiles (used to build entries) fetches every file in parallel via
fetchRepoFile which can spike memory/IO; introduce a concurrency limiter (e.g.,
p-limit) around the markdownFiles.map work so only N fetchRepoFile calls run at
once, wrap the async mapping logic (the canonicalPath check, fetchRepoFile,
extractFrontMatter and subsequent redirect mapping) in the limiter and await
Promise.all on the limited tasks instead of raw Promise.all; reference the
Promise.all usage, fetchRepoFile, getCanonicalDocsPath, extractFrontMatter and
normalizeDocsRedirectPath when updating the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs-info.md`:
- Around line 49-56: The example code block in docs-info.md is malformed: the
inner TypeScript JSX snippet (export function App()) ends with `;``` instead of
the proper closing triple backticks; update the closing fence for the inner
```tsx title="app.tsx" block so the example ends with ``` (three backticks)
followed by the outer markdown fences, ensuring the `export function App()`
snippet is properly closed.

---

Nitpick comments:
In `@src/routes/blog`.$.tsx:
- Around line 26-54: The redirect manifest is rebuilt on each call to
handleRedirects; move the build out of the function so it runs once at module
load: compute redirectEntries from allPosts and call buildRedirectManifest to
produce a cached redirectOwners (or similar top-level constant) and have
handleRedirects use that cached redirectOwners (keep normalizeBlogRedirectPath,
normalizedPaths, and the final redirect throw logic intact so only the manifest
construction is relocated).

In `@src/utils/docs.ts`:
- Around line 177-209: The current Promise.all over markdownFiles (used to build
entries) fetches every file in parallel via fetchRepoFile which can spike
memory/IO; introduce a concurrency limiter (e.g., p-limit) around the
markdownFiles.map work so only N fetchRepoFile calls run at once, wrap the async
mapping logic (the canonicalPath check, fetchRepoFile, extractFrontMatter and
subsequent redirect mapping) in the limiter and await Promise.all on the limited
tasks instead of raw Promise.all; reference the Promise.all usage,
fetchRepoFile, getCanonicalDocsPath, extractFrontMatter and
normalizeDocsRedirectPath when updating the code.
🪄 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: 9fa0a660-5190-4142-b3db-acb1d7c90ed0

📥 Commits

Reviewing files that changed from the base of the PR and between 88a8f5d and 708f666.

📒 Files selected for processing (8)
  • content-collections.ts
  • docs-info.md
  • src/routes/$libraryId/$version.docs.$.tsx
  • src/routes/$libraryId/$version.docs.framework.$framework.$.tsx
  • src/routes/blog.$.tsx
  • src/utils/docs.ts
  • src/utils/documents.server.ts
  • src/utils/redirects.ts

@LadyBluenotes LadyBluenotes changed the title add redirects into frontmatter add redirects into frontmatter & update deps Mar 30, 2026
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.

🧹 Nitpick comments (1)
package.json (1)

155-157: Node 20+ requirement is a breaking change for contributors and deployments.

Bumping the minimum Node version from 18 to 20 is reasonable since Node 20 is the current LTS. Ensure CI/CD pipelines, deployment environments (Netlify), and contributor documentation are updated to reflect this requirement.

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

In `@package.json` around lines 155 - 157, The package.json engines field now
requires Node ">=20.0.0", which is a breaking change for contributors and
deployments; update all relevant configurations and docs to match this new
minimum by: changing CI workflows (GitHub Actions, etc.) and any Dockerfiles to
use Node 20 images, updating deployment platform settings (e.g., Netlify
runtime/node version) to Node 20, and revising contributor/setup docs and README
to state the Node >=20 requirement; reference the package.json "engines" entry
when making these edits so everything stays consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@package.json`:
- Around line 155-157: The package.json engines field now requires Node
">=20.0.0", which is a breaking change for contributors and deployments; update
all relevant configurations and docs to match this new minimum by: changing CI
workflows (GitHub Actions, etc.) and any Dockerfiles to use Node 20 images,
updating deployment platform settings (e.g., Netlify runtime/node version) to
Node 20, and revising contributor/setup docs and README to state the Node >=20
requirement; reference the package.json "engines" entry when making these edits
so everything stays consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b5d04ba-0e86-4031-a288-beef859f6155

📥 Commits

Reviewing files that changed from the base of the PR and between 2afc1ac and 4304756.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 138: Update the Node engine constraint in package.json: replace the
current "node": ">=20.0.0" entry with "node": "^20.19.0 || >=22.12.0" so the
engines field matches the requirements of vite@8, `@vitejs/plugin-react`@6 and
oxlint@1.57.0; edit the "engines" -> "node" property accordingly and ensure
package.json remains valid JSON after the change.

In `@src/components/Doc.tsx`:
- Around line 124-127: In the Doc component, the anchor uses pagePath (from
DocProps) without checking for undefined, which can produce href="undefined.md";
guard this by only rendering the <a> (or computing its href) when pagePath is
truthy: update the Doc component's render logic around the anchor that
references {pagePath}.md to conditionally render the anchor (or set a safe
fallback) so the href is never built from an undefined pagePath.
🪄 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: 6df725f0-a743-461d-bb43-0ad8a4c3d3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 05a5bd5 and 9ef4714.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • .oxlintrc.json
  • eslint.config.mjs
  • package.json
  • src/components/Doc.tsx
  • src/components/DocsLayout.tsx
  • src/components/StackBlitzEmbed.tsx
💤 Files with no reviewable changes (1)
  • eslint.config.mjs
✅ Files skipped from review due to trivial changes (1)
  • src/components/StackBlitzEmbed.tsx

package.json Outdated
},
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
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

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify published engine constraints for upgraded tooling.
npm view vite@8 engines --json
npm view `@vitejs/plugin-react`@6 engines --json
npm view oxlint@1.57.0 engines --json

Repository: TanStack/tanstack.com

Length of output: 378


Update Node engine constraint to match toolchain requirements.

The current "node": ">=20.0.0" is incompatible with the upgraded toolchain. vite@8, @vitejs/plugin-react@6, and oxlint@1.57.0 all require "node": "^20.19.0 || >=22.12.0". Change the constraint to "node": "^20.19.0 || >=22.12.0" to prevent install/build failures on Node versions 20.0.0 through 20.18.x.

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

In `@package.json` at line 138, Update the Node engine constraint in package.json:
replace the current "node": ">=20.0.0" entry with "node": "^20.19.0 ||
>=22.12.0" so the engines field matches the requirements of vite@8,
`@vitejs/plugin-react`@6 and oxlint@1.57.0; edit the "engines" -> "node" property
accordingly and ensure package.json remains valid JSON after the change.

Comment on lines +124 to 127
<a href={`${pagePath}.md`} className="sr-only">
AI/LLM: This documentation page is available in plain markdown format at
{pagePath}.md
</a>
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 | 🟡 Minor

Guard the markdown link when pagePath is absent.

pagePath is optional in DocProps, so this can produce href="undefined.md".

🐛 Proposed fix
-      <a href={`${pagePath}.md`} className="sr-only">
-        AI/LLM: This documentation page is available in plain markdown format at
-        {pagePath}.md
-      </a>
+      {pagePath ? (
+        <a href={`${pagePath}.md`} className="sr-only">
+          AI/LLM: This documentation page is available in plain markdown format at
+          {pagePath}.md
+        </a>
+      ) : null}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a href={`${pagePath}.md`} className="sr-only">
AI/LLM: This documentation page is available in plain markdown format at
{pagePath}.md
</a>
{pagePath ? (
<a href={`${pagePath}.md`} className="sr-only">
AI/LLM: This documentation page is available in plain markdown format at
{pagePath}.md
</a>
) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Doc.tsx` around lines 124 - 127, In the Doc component, the
anchor uses pagePath (from DocProps) without checking for undefined, which can
produce href="undefined.md"; guard this by only rendering the <a> (or computing
its href) when pagePath is truthy: update the Doc component's render logic
around the anchor that references {pagePath}.md to conditionally render the
anchor (or set a safe fallback) so the href is never built from an undefined
pagePath.

@tannerlinsley tannerlinsley merged commit 460010c into main Mar 30, 2026
7 of 8 checks passed
@tannerlinsley tannerlinsley deleted the redirect-frontmatter branch March 30, 2026 22:28
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.

2 participants