Skip to content

fix(webapp): use blocking fallback for ISR pages to fix SEO#5676

Merged
nimrodkra merged 3 commits intomainfrom
fix/isr-fallback-blocking-seo
Mar 8, 2026
Merged

fix(webapp): use blocking fallback for ISR pages to fix SEO#5676
nimrodkra merged 3 commits intomainfrom
fix/isr-fallback-blocking-seo

Conversation

@nimrodkra
Copy link
Member

@nimrodkra nimrodkra commented Mar 8, 2026

Summary

  • Switch fallback: truefallback: 'blocking' on posts, tags, and sources pages
  • With fallback: true, crawlers receive an empty skeleton HTML (~2.5KB) with generic title, no canonical, no OG tags, no H1, and no content
  • With fallback: 'blocking', Next.js waits for getStaticProps to complete before responding, ensuring crawlers always get the fully rendered page (~12KB+) with all SEO metadata

Context

Ahrefs site audit (Mar 5) found ~18,753 pages flagged across multiple SEO categories — all caused by the same root issue:

Issue Count
Orphan pages (no internal links) 19,503
Missing H1 tag 18,958
Low word count 18,796
Duplicate pages without canonical 18,753
Incomplete OG tags 18,753
No outgoing links 18,754

All are post/tag/source pages from sitemaps that Ahrefs crawled during ISR fallback, receiving skeleton HTML instead of rendered content.

Test plan

  • Verify posts, tags, and sources pages still load correctly
  • Check that first-visit to an uncached post/tag/source serves full HTML (not skeleton)
  • Confirm meta tags, canonical, OG tags, and H1 are present in initial HTML response
  • Monitor TTFB — first requests to uncached pages will be slightly slower (blocking instead of immediate skeleton), but subsequent requests are unchanged

🤖 Generated with Claude Code

Preview domain

https://fix-isr-fallback-blocking-seo.preview.app.daily.dev

Switch posts, tags, and sources pages from `fallback: true` to
`fallback: 'blocking'` so crawlers always receive fully rendered HTML
with proper meta tags, canonical URLs, OG tags, and structured data
instead of an empty skeleton page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Mar 8, 2026 10:12am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Mar 8, 2026 10:12am

Request Review

@nimrodkra
Copy link
Member Author

@claude please review this pr

@claude
Copy link
Contributor

claude bot commented Mar 8, 2026

Claude finished @nimrodkra's task in 1m 30s —— View job


PR Review

  • Gather context and understand the request
  • Read changed files
  • Analyze the changes
  • Post review feedback

Summary

The fix is correct and well-motivated. Switching from fallback: true to fallback: 'blocking' is the right approach to fix the SEO issue — crawlers will now always receive a fully-rendered page with all metadata instead of a skeleton HTML.


Observations

✅ Core change is correct

All three getStaticPaths functions are correctly updated. With fallback: 'blocking', Next.js will SSR the page on first request and cache it, so any crawler hitting an uncached URL gets the full HTML immediately.

⚠️ Dead code: isFallback checks are now unreachable

With fallback: 'blocking', router.isFallback is always false — Next.js never puts the page in the fallback state. The existing isFallback checks in all three components are now dead code:

posts/[id]/index.tsx line 216:

if (isLoading || isFallback || privateSourceJoin.isActive) {
  // isFallback is always false now

sources/[source].tsx lines 162–167:

if (!isFallback && !source) {      // simplifies to: if (!source)
  return <Custom404 />;
}
if (isFallback || !source) {       // isFallback branch is dead
  return <></>;
}

tags/[tag].tsx lines 234–246:

if (isFallback) {                  // always false now — entire block is dead
  return (
    <FeedPageLayoutComponent>
      <PageInfoHeader ...>...</PageInfoHeader>
    </FeedPageLayoutComponent>
  );
}

These dead branches won't cause bugs, but they're misleading and should be cleaned up as a follow-up (or in this PR). Fix this →

ℹ️ Pre-existing: Sources page returns soft 404

sources/[source].tsx getStaticProps (line 311) returns { props: { source: null }, revalidate: 60 } for not-found/forbidden sources, instead of { notFound: true }. This means the response is HTTP 200 with a Custom404 component — a "soft 404" that search engines may not treat as a true 404. The posts page already uses notFound: true correctly at line 348–351. Not introduced by this PR, but worth a follow-up for SEO consistency. Fix this →

ℹ️ TTFB impact acknowledged

First-time requests to uncached pages will block until getStaticProps completes. Given the parallel API fetches already in place (posts page fetches data and comments concurrently), this should be acceptable. Worth monitoring after deploy.


Verdict

LGTM — the fix is minimal, correct, and directly addresses a real SEO regression. The isFallback cleanup is a nice-to-have but not a blocker.

Address PR review feedback:
- Remove dead isFallback checks from posts, sources, and tags pages
  since fallback: 'blocking' means isFallback is always false
- Fix sources page soft 404: return { notFound: true } instead of
  { props: { source: null } } so search engines see a proper 404

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nimrodkra
Copy link
Member Author

@claude let's run a quick 2nd round of review

@claude
Copy link
Contributor

claude bot commented Mar 8, 2026

Claude finished @nimrodkra's task in 1m 20s —— View job


Second Round Review

  • Read changed files
  • Verify isFallback cleanup
  • Verify sources soft 404 fix
  • Post review feedback

All issues flagged in the first review have been addressed. Here's the second-pass breakdown:

✅ sources/[source].tsx — fully cleaned up

  • useRouter import removed (no longer needed) ✅
  • isFallback removed from the component entirely ✅
  • Dead if (isFallback || !source) return <></>; block removed ✅
  • Soft 404 fixed: getStaticProps now returns { notFound: true, revalidate: 60 } instead of { props: { source: null } } for not-found/forbidden sources ✅

✅ tags/[tag].tsx — fully cleaned up

  • isFallback removed from useRouter() destructure ✅
  • Entire fallback skeleton UI block removed ✅

⚠️ posts/[id]/index.tsx — cleanup is partial but intentional

The isFallback variable is now const isFallback = false; (hardcoded) rather than removed:

// line 167
const isFallback = false;

// line 253
<Content ... isFallback={isFallback} ... />

This is correct because isFallback prop still flows through the ContentPostContentBasePostContent chain, and BasePostContent uses it as a guard:

// BasePostContent.tsx:39
if (!id && !isFallback) {
  return <Custom404 />;
}

With fallback: 'blocking', post will always be hydrated server-side so id will always exist, making isFallback permanently false and harmless. The hardcoded false is an honest representation of the new runtime behavior. Full cleanup of the isFallback prop from the shared component tree (PostContent, BasePostContent, SquadPostContent, etc.) would be a broader refactor — fine to defer.


Verdict

LGTM — the second commit cleanly addresses all first-round feedback. The fix is minimal, correct, and all three pages are in good shape.

The query variable from useRouter() was only used in the removed
isFallback block. ESLint correctly flags it as unused.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nimrodkra nimrodkra merged commit 44073d8 into main Mar 8, 2026
12 checks passed
@nimrodkra nimrodkra deleted the fix/isr-fallback-blocking-seo branch March 8, 2026 10:48
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