Skip to content

SEO Fix Plan — all 12 tickets - #94

Open
shivthakker wants to merge 2 commits into
mainfrom
seo/technical-fixes
Open

SEO Fix Plan — all 12 tickets#94
shivthakker wants to merge 2 commits into
mainfrom
seo/technical-fixes

Conversation

@shivthakker

Copy link
Copy Markdown
Member

Implements the Fleetbase SEO Fix Plan (Ahrefs audit, project 9822696, crawl 2 Sep 2026). All 12 tickets addressed across two commits.

Every claim in the plan was verified against the live site before being acted on. Four turned out not to reproduce — those are documented below rather than "fixed", because the code was already correct.

Phase 1 — stop the bleeding

Status
SEO-01 ?ref= duplicates Diagnosis did not reproduce. The plan says 16 URLs self-canonicalise with the parameter. I checked seven — all seven already returned a clean canonical. Shipped the redirect anyway as hardening: middleware now 308s ?ref= away so those URLs can't be crawled as pages. The five stale paths it lists already redirect.
SEO-02 Console indexing Confirmed, worse than described. console.fleetbase.io/robots.txt serves Disallow: — empty, i.e. allow everything — and the shell has no title or robots meta. The console app is a different repo, so what's fixed here is the link equity: every CTA to it now carries rel="nofollow" via a shared relForHref helper. The noindex work still needs a PR against the console repo.
SEO-03 Blog robots.txt 500 Confirmed; root cause broader than stated. fetchGhost threw on any non-OK response, and Ghost answers an unknown slug with a 404 — so every bad /blog/* URL returned a 500, not just robots.txt. Slug lookups now treat 404 as "no such post" and fall through to notFound().

Phase 2 — one template pass

Status
SEO-04 Double brand suffix 52 pages hardcoded | Fleetbase while the layout appends it again. Stripped from the top-level title only — openGraph.title and twitter.title are not templated by Next.js, so they keep the brand. 16 over-length titles trimmed. Long blog headlines now return an absolute title, dropping the suffix instead of cutting the headline.
SEO-05 Descriptions 57 rewritten by hand to 120–155 chars — these are sales pages, so truncation would have been wrong. Blog excerpts (~500 chars from Ghost) truncate at a word boundary. The three auth screens got noindex instead of padded copy.
SEO-06 SoftwareApplication schema Won't fix — documented at the schema. Every property the plan asks for is already present. The only gap is aggregateRating, which Google requires to be backed by real reviews. Inventing one would be fabricating data and is a manual-action risk. There was already a deliberate comment in the codebase refusing this.
SEO-07 Open Graph Claim is incorrect. The plan says only blog posts have OG tags; /pricing on the live site returns a full set, and 67 page files already set openGraph. The genuine gaps were the /compare hub and all 11 docs routes, which now share a buildDocsMetadata helper.

Phase 3 — housekeeping

Status
SEO-08 Redirect/404 links Verified every destination live first — several differ from the plan's assumption. 10 files rewritten, both internal 404s fixed, Bringg card removed from /compare (it linked to a redirect back to the same page). The 20 external 4XX links are not done — that list exists only in the Ahrefs export.
SEO-09 Broken icons All four already existed under public/images/integrations; the component just pointed at the CDN. Repointed all 26 references and self-hosted the remaining 18 (Simple Icons, CC0). Zero CDN dependencies left.
SEO-10 Docs double H1 Not Docusaurus — the docs are fumadocs. fumadocs renders the frontmatter title as an h1, and 354 MDX files then opened with their own # heading — identical text in 269 cases. Removed from 343 files; the 11 section roots keep theirs, since those routes skip DocsTitle.
SEO-11 Blog TTFB Already on ISR — the "rendered on every request" diagnosis was wrong. The real cost was a 5-minute revalidate window; raised to an hour with s-maxage=3600 + stale-while-revalidate.
SEO-12 Internal linking Footer column covering all 12 role/use-case pages, a homepage section linking the three compare pages and every role/use-case page in body copy, and a RelatedPages component cross-linking platform modules to the roles and industries that buy them.

Verification

SEO-04  titles ending in a doubled brand suffix : 0   (target 0)
SEO-04  rendered titles over 60 chars           : 3   (target <20)
SEO-05  descriptions outside 120-155            : 0   (target 0)
SEO-09  remaining CDN icon references           : 0   (target 0)
SEO-10  docs pages with a duplicate body h1     : 0   (target 0)
SEO-12  role/use-case pages: 1 inbound link -> 5-6 source refs, plus site-wide footer

next build compiles successfully.

Reviewer notes — please look at these

  1. The three compare-page titles are deliberately left over 60 characters. They are the only pages on the site earning commercial rankings (fix: restore docs sidebar access on mobile #9, switched to pnpm #3, Install and Configure Vercel Web Analytics #8 for "[competitor] alternatives"). Retitling them risks that for a warning the plan itself says is acceptable, and the growth plan (GP-03/GP-08) rewrites them anyway. Happy to trim if you'd rather.
  2. rel="nofollow" on console CTAs is marked optional in the plan. It's included because you asked for the full document, but note that nofollow drops equity rather than redistributing it — the real win is the console noindex, which is in another repo.
  3. 343 docs content files changed. The diff is pure deletion of a duplicated heading; worth a spot-check that nothing reads oddly.
  4. Not verifiable locally: the build cannot complete static generation without GHOST_API_URL / GHOST_CONTENT_API_KEY, so the blog routes are compile-checked only. This is pre-existing on main.

Follow-ups this PR does not cover

  • SEO-02 console noindex — needs a PR against the console repo
  • SEO-03 subdomain redirectblog.fleetbase.iofleetbase.io/blog is hosting-level config, not in this repo
  • SEO-08 external 4XX — needs the Ahrefs "External 4XX" export

🤖 Generated with Claude Code

shivthakker and others added 2 commits September 3, 2026 13:01
Phase 1 — stop the bleeding

SEO-01  Strip ?ref=blog.fleetbase.io duplicates. Canonicals already
        pointed at the clean path on every URL checked, so the fix is
        hardening rather than repair: middleware now 308s the parameter
        away so those URLs cannot be crawled as pages at all. The five
        stale paths the plan lists already redirect in next.config.ts.

SEO-02  Keep the console out of Google. The console app itself lives in
        another repo; what belongs here is the link equity. Every CTA
        pointing at console.fleetbase.io now carries rel="nofollow",
        via a shared relForHref helper so the two navbar render sites,
        the solutions layout and 27 direct links stay consistent.

SEO-03  Fix the blog robots.txt 500. Root cause was broader than the
        plan describes: fetchGhost threw on any non-OK response, and
        Ghost answers an unknown slug with a 404 — so *every* bad
        /blog/* URL returned a 500, not just robots.txt. Slug lookups
        now treat 404 as "no such post" and fall through to notFound().

Phase 2 — one template pass

SEO-04  52 pages hardcoded "| Fleetbase" while the root layout appends
        it again via title.template. Stripped from the top-level title
        only — openGraph.title and twitter.title are not templated, so
        they keep the brand. 16 over-length titles trimmed under 60
        characters. Long blog headlines now return an absolute title,
        dropping the suffix rather than cutting the headline.

SEO-05  57 descriptions rewritten to 120–155 characters, hand-written
        per page rather than truncated, since these are sales pages.
        Blog excerpts (~500 chars from Ghost) truncate at a word
        boundary. The three auth screens get noindex instead of padded
        copy — they have no search value.

SEO-06  Won't-fix, documented at the schema. Every property the plan
        asks for is already present; the only gap is aggregateRating,
        which Google requires to be backed by real reviews. Inventing
        one would be fabricating data.

SEO-07  The plan says only blog posts have Open Graph tags; that is not
        true of the live site — 67 pages already had them. The genuine
        gaps were the /compare hub and all 11 docs routes, which now
        share a buildDocsMetadata helper adding canonical, OG and
        Twitter cards built from each page's frontmatter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SEO-08  Point internal links at final URLs. Verified each redirect
        against the live site first — several went somewhere other than
        the plan assumed (/docs/fleet-ops/orders lands on
        /docs/fleet-ops/operations/orders/overview, not /orders/overview).
        Rewrote 10 files, fixed the two internal 404s, and removed the
        Bringg card from /compare, which linked to a redirect back to
        the page the visitor was already on. Growth plan GP-05 restores
        it as a real page.

        Not done: the 20 external 4XX links. That list only exists in
        the Ahrefs export, which is not in the repo.

SEO-09  Self-host the integration icons. All four broken logos already
        existed under public/images/integrations — the component was
        simply pointing at the CDN. Repointed all 26 references and
        self-hosted the remaining 18 (Simple Icons, CC0) so a sales page
        does not depend on a CDN that renamed these slugs once already.

SEO-10  One H1 per docs page. Not a Docusaurus swizzle — the docs are
        fumadocs. fumadocs renders the frontmatter title as an h1 and
        354 MDX files then opened with their own "# " heading, which is
        the same text 269 times over. Removed the redundant body heading
        from 343 files; the 11 section roots keep theirs, because those
        routes skip DocsTitle and it is their only h1.

SEO-11  Cache blog pages at the edge. The blog was already on ISR, so
        the plan's "rendered on every request" diagnosis was wrong. The
        real cost was a 5-minute revalidate window: raised to an hour
        and paired with s-maxage=3600 plus stale-while-revalidate so the
        CDN never makes a reader wait on a cold render.

SEO-12  Link the sales pages from where the authority is. The role and
        use-case pages had one inbound link each because they lived only
        in the nav dropdown. Added a footer column covering all twelve,
        a homepage section linking the three compare pages and every
        role and use-case page in body copy, and a RelatedPages
        component that cross-links platform modules to the roles and
        industries that buy them. It looks itself up by pathname, so
        pairings live in one map instead of thirty page components.

Also caught in review: /platform/fleetops kept a doubled brand suffix
because its title came from a const shared with the social tags, and two
further titles repeated the brand mid-string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
fleetbase-io Ready Ready Preview Sep 3, 2026 5:18am UTC

Request Review

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