Uh oh!
There was an error while loading. Please reload this page.
refactor(blog): split AEO/GEO content into a new /library section - #5516
Conversation
Blog was mixing hand-written editorial posts with listicle/comparison/how-to posts optimized for answer engines. Extract a generic content engine (lib/content) shared by both sections, move the 6 AEO/GEO posts into a new /library route tree, and keep /blog editorial-only. - lib/content: generic registry factory, MDX components, SEO builders - lib/blog + lib/library: thin per-section instantiations over the shared engine - app/(landing)/library: mirrors the blog route tree via shared Content*Page components - app/sitemap.ts, app/robots.ts, navbar: updated for the new section - next.config.ts: permanent redirects from moved /blog/<slug> URLs to /library/<slug>
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Blog routes are slimmed down to data + metadata; layout, loading skeletons, and SEO wiring move into reusable Six posts live under Discovery updates: Library in the Resources nav, Reviewed by Cursor Bugbot for commit e32db6f. Configure here. |
Uh oh!
There was an error while loading. Please reload this page.
Greptile SummaryThis PR refactors the blog content pipeline by extracting a generic shared
Confidence Score: 5/5Safe to merge — this is a structural refactor with no runtime logic changes; the shared component architecture is consistent, canonical URLs are correctly updated, and SEO redirects are in place. The shared registry factory, SEO builders, and Content*Page components are well-parameterized. MDX frontmatter canonical URLs were updated to /library/ paths, the 6 permanent redirects preserve existing indexed URLs, and sitemap.ts/robots.ts are correctly extended. The only noteworthy design wrinkle is that invalidateCaches() clears the module-level author cache shared between both registries, but the function has no active call site in the codebase, so this has no current effect. apps/sim/lib/content/registry-factory.ts — the invalidateCaches implementation has a subtle cross-registry side effect worth reviewing if cache invalidation is ever wired up. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
Factory[createContentRegistry] -->|blog instance| BR[blogRegistry]
Factory -->|library instance| LR[libraryRegistry]
BR -->|shared authorsDir| AC[(authorsCacheByDir module-level)]
LR -->|shared authorsDir| AC
BR --> BM[cachedMeta - blog]
LR --> LM[cachedMeta - library]
BR --> BlogIndex[blog index and slug pages]
BR --> BlogRSS[blog rss.xml]
LR --> LibIndex[library index and slug pages]
LR --> LibRSS[library rss.xml]
LR --> LibSitemap[library sitemap-images.xml]
BR --> Sitemap[sitemap.xml]
LR --> Sitemap
OldBlog[old blog slug URLs] -->|301 permanent redirect| LibSlug[library slug pages]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
Factory[createContentRegistry] -->|blog instance| BR[blogRegistry]
Factory -->|library instance| LR[libraryRegistry]
BR -->|shared authorsDir| AC[(authorsCacheByDir module-level)]
LR -->|shared authorsDir| AC
BR --> BM[cachedMeta - blog]
LR --> LM[cachedMeta - library]
BR --> BlogIndex[blog index and slug pages]
BR --> BlogRSS[blog rss.xml]
LR --> LibIndex[library index and slug pages]
LR --> LibRSS[library rss.xml]
LR --> LibSitemap[library sitemap-images.xml]
BR --> Sitemap[sitemap.xml]
LR --> Sitemap
OldBlog[old blog slug URLs] -->|301 permanent redirect| LibSlug[library slug pages]
Reviews (4): Last reviewed commit: "fix(blog): RSS lastBuildDate reflects th..." | Re-trigger Greptile |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Author pages for unknown/unmatched ids fell back to author?.id, which is
undefined when no post matches — collapsing canonical/OG URLs to
{basePath}/authors/ instead of {basePath}/authors/{id}. Pass the route id
explicitly so canonical always resolves correctly.waleedlatif1
commented
Jul 8, 2026
Fixed in ef7c0b1 — |
… cache, dead-export docs - RSS lastBuildDate now uses updated ?? date so edits after publication are reflected, matching the per-item pubDate semantics (blog + library) - Author JSON is now cached once per authorsDir at module scope instead of once per registry instantiation, since blog and library point at the same directory - Documented getNavPosts as reserved-but-unwired, matching the existing PLATFORM_MENU/SOLUTIONS_MENU convention in the navbar
waleedlatif1
commented
Jul 8, 2026
waleedlatif1
commented
Jul 8, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 91a7a3f. Configure here.
A featured post older than POSTS_PER_PAGE would appear both on page 1's featured row (sorted to the front) and again on its natural date-sorted page, since only page 1 excluded featured posts from the remaining list. Carve featured posts out of the paginated pool up front so pagination stays consistent and duplicate-free across all pages.
waleedlatif1
commented
Jul 8, 2026
Addressed the pagination dedup bug Greptile flagged in its summary (confidence 4/5) — fixed in 43bc002. |
waleedlatif1
commented
Jul 8, 2026
waleedlatif1
commented
Jul 8, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
…west item lastBuildDate only looked at items[0]'s updated/date, so revising an older post already in the feed wouldn't advance it. Extract the max-across-posts logic sitemap.ts already had into a shared lib/content/utils helper (latestModified) and use it in both RSS routes and the sitemap, so a revision to any feed item is reflected.
waleedlatif1
commented
Jul 8, 2026
waleedlatif1
commented
Jul 8, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e32db6f. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
…-*-page components The blog/library split (#5516) moved the blog post/index/author JSX into shared ContentPostPage/ContentIndexPage/ContentAuthorPage components while this branch was in flight, so the original unoptimized removal (verified local-only ogImage paths for both blog and library content) needs to land on those shared components instead of the old per-route JSX.
… pages (#5522) * fix(landing): fix Core Web Vitals regressions across public marketing pages - root layout unconditionally rendered next-runtime-env's PublicEnvScript, which calls unstable_noStore() and silently forced every route in the app dynamic - marketing pages never got static/ISR caching despite their own revalidate. Gated it to self-hosted only; hosted now uses a static, build-time equivalent (app/_shell/public-env-script.tsx) - removed real pointer-drag handlers from the hero's decorative workflow animation (was draggable despite being aria-hidden) - disabled dragging/panning on the (currently unmounted) landing-preview ReactFlow canvas so it's static-by-default if it's ever wired in - lazy-mount the Product Demo section's duplicate HeroVisual instance via next/dynamic + IntersectionObserver instead of loading it eagerly below the fold - disabled Next.js Link prefetch on always-in-viewport /signup and /login CTAs (navbar, hero, mobile nav) so their JS isn't fetched on every pageview regardless of whether the visitor clicks - removed `unoptimized` from local blog/integration images (including the priority LCP image on every blog post), letting next/image serve resized AVIF/WebP instead of full-size originals * fix(landing): address review findings on the CWV PR - revert prefetch={false} on below-fold CTAs (cta.tsx, enterprise.tsx) - contradicts the prefetch-on-approach rule this PR itself documents - restore unoptimized on avatarUrl and the MDX body-image renderer, both of which can legitimately hold external URLs outside next.config.ts's image remotePatterns allow-list - simplify handleAnchors to a single block argument now that positions are static (the second "live position" argument was always identical to the first after the drag-handler removal) - extract the near-duplicate IntersectionObserver lazy-mount logic shared by landing-preview-mount.tsx and product-demo-visual-mount.tsx into a single apps/sim/app/(landing)/hooks/use-lazy-mount.ts hook - import next-runtime-env's own exported PUBLIC_ENV_KEY constant instead of a hardcoded string literal, and match its case-insensitive NEXT_PUBLIC_ filter exactly, removing any drift risk between the two implementations - drop plain inline comments with no TSDoc home in favor of relying on the existing TSDoc/CLAUDE.md documentation * fix(landing): carry the unoptimized-image fix onto the shared content-*-page components The blog/library split (#5516) moved the blog post/index/author JSX into shared ContentPostPage/ContentIndexPage/ContentAuthorPage components while this branch was in flight, so the original unoptimized removal (verified local-only ogImage paths for both blog and library content) needs to land on those shared components instead of the old per-route JSX. * fix(landing): document ogImage's local-path expectation, fix CLAUDE.md structure doc - add a one-line comment on ContentFrontmatterSchema.ogImage documenting that it's rendered without unoptimized and expects a local path, matching the existing avatarUrl comment convention (a reviewer noted ogImage's schema is technically unconstrained and seo.ts has an http-prefix branch, though all current content is local) - add the new hooks/ folder to the (landing) CLAUDE.md structure diagram and name use-lazy-mount.ts directly in the lazy-mount rule * fix(landing): escape </script> breakout in the static public-env script Greptile P1: a NEXT_PUBLIC_* value containing "</script>" would close the inline script early and could inject markup/script into every hosted page. Escape "<" in the serialized JSON before interpolating it, matching the standard JSON-in-script-tag safeguard.
Summary
lib/content: registry factory, MDX components, SEO builders) instantiated separately forlib/blogandlib/librarybest-zapier-alternatives,ai-agents-vs-rpa,ai-agent-vs-chatbot,openai-vs-n8n-vs-sim,ai-agent-ideas,how-to-create-an-ai-agent) into a newcontent/library/tree/libraryas an exact route-tree mirror of/blog(index,[slug], tags, authors, rss.xml, sitemap-images.xml), rendered through sharedContent*Pagecomponents so both sections stay byte-identical in layoutapp/sitemap.ts,app/robots.ts, and the navbar Resources menu for the new section/blog/<slug>URLs to/library/<slug>to preserve existing SEO/indexingType of Change
Testing
bun run type-checkclean (only pre-existing unrelated@aws-sdk/client-textracterrors)bunx biome check .clean across full repoNEXT_PUBLIC_APP_URL, unrelated to this change)/blog/, zero dangling imports to deletedlib/blogfilesChecklist