StatsHub SEO Audit Report
A full technical SEO review of the web app as it stood on 2026-01-29 — per-page grades, missing schema, and the fixes proposed at the time.
Date: January 29, 2026 Framework: Next.js 14.2.18 (Pages Router) Pages Audited: 32 (30 routes + 2 global files)
Executive Summary
Overall Health: ⚠️ Mixed
StatsHub has strong SEO foundations on key pages (homepage, blog, betting-academy, referees, stats-explained, odds-converter) but significant gaps on tool pages and the entire basketball section.
Top 5 Priority Issues
- Basketball pages have zero SEO — All 3 basketball dynamic routes (fixture, player, team) use generic football-focused MetaTags with no dynamic data, no structured data, and hardcoded og to the homepage. Grade: F
- Tool pages missing all SEO tags —
/prop-screener,/team-screener, and/value-betshave no title, meta description, canonical URL, OG tags, or structured data. Grade: F - MetaTags component og bug — The default
MetaTagscomponent hardcodesog:urltohttps://statshub.com/for all pages, causing incorrect OG URLs across 8+ pages. - Client-side rendering on indexable pages — Most static content pages (features, free-tools, odds-converter, player-stats, team-stats) use CSR instead of SSG, meaning crawlers may not see content.
- League/Team dynamic meta tags invisible to crawlers — Rich meta tag data (season, team count, champion) depends on client-side SWR fetches, so SSR HTML lacks this information.
Quick Wins
- Add
<Head>blocks with title/description to/prop-screener,/team-screener,/value-bets(~30 min) - Fix
/lineup-botcanonical fromstatshub.iotostatshub.com(~1 min) - Add
<meta name="robots" content="noindex" />to/404and/favourites(~5 min) - Fix
'HTML entities in meta descriptions on/free-toolsand/odds-converter(~5 min) - Add
getStaticPropsto purely static pages like/features,/free-tools,/odds-converter(~15 min each)
Global Files Audit
_document.tsx
File: src/pages/_document.tsx
| Check | Status | Details |
|---|---|---|
| HTML lang | ✅ | <Html lang="en"> properly set |
| Viewport | ✅ | Configured with viewport-fit=cover for notch support |
| Favicons | ✅ | 32x32, 16x16, apple-touch-icon all present |
| PWA Manifest | ✅ | /manifest.json linked |
| PWA Meta Tags | ✅ | application-name, theme-color, apple-mobile-web-app-capable |
| Analytics | ✅ | Google Analytics (G-FNRHWN8908) + Umami analytics |
| Default Title | ❌ | No default <title> fallback — pages without Head will show no title |
Issues:
- Viewport meta tag is in
_document.tsxHead, but Next.js recommends putting viewport in_app.tsxor per-page. Having it here works but triggers a Next.js warning. - No default
<title>tag — pages that forget to add their own will have no title.
Recommendations:
- Add a default
<title>StatsHub | Free Football & Basketball Stats</title>as a fallback. - Consider moving viewport to
next.config.jsviewport export.
_app.tsx
File: src/pages/_app.tsx
| Check | Status | Details |
|---|---|---|
| Error Boundary | ✅ | Wraps entire app |
| Theme Support | ✅ | next-themes with system default |
| Layout System | ✅ | Per-page layouts via getLayout |
| SWR Config | ✅ | Deduplication and revalidation configured |
| Global SEO | ❌ | No global meta tags, no DefaultSeo component |
Issues:
- No global SEO defaults (e.g.,
next-seo'sDefaultSeo) — each page must independently define all meta tags.
Recommendations:
- Consider adding a global SEO defaults component that provides fallback title, description, OG tags for any page that doesn't override them.
Static Pages
/
File: src/pages/index.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | "Free Football & Player Stats -- Shots, Tackles + More | StatsHub" (64 chars — slightly over 60) |
| Meta Description | ✅ | Present, ~170 chars. Slightly over 160 but keyword-rich |
| Open Graph Tags | ✅ | Full suite: og, og, og, og, og, og, og, og dimensions |
| Twitter Card Tags | ✅ | Full suite: twitter, twitter, twitter, twitter, twitter, twitter |
| Canonical URL | ✅ | Self-referencing: https://statshub.com |
| H1 Tag | ⚠️ | "Today's Football Fixtures, Player Stats & Team Stats" — keyword-rich, but two H1 tags on page |
| Heading Hierarchy | ⚠️ | Two H1s detected. H2/H3 follow logically after the first H1 |
| Structured Data | ✅ | Excellent — WebSite, FAQPage, Organization, and dynamic SportsEvent ItemList schemas |
| Image Alt Text | ✅ | Tournament logos, team logos all have alt attributes |
| Rendering | ⚠️ | CSR only — fixture data fetched client-side, not visible to crawlers |
Issues:
- Two H1 tags on the page
- Title 64 chars (slightly over 60)
- Meta description ~170 chars (over 160)
- CSR — fixture data invisible to crawlers
Recommendations:
- Reduce to single H1
- Trim title to under 60 chars
- Trim description to 150-160 chars
- Add getServerSideProps or getStaticProps with ISR for fixture data
/100-club
File: src/pages/100-club.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | Generic default from MetaTags — not specific to 100% Club |
| Meta Description | ⚠️ | Generic default — not page-specific |
| Open Graph Tags | ⚠️ | Generic, og points to homepage |
| Twitter Card Tags | ⚠️ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "100% CLUB" |
| Heading Hierarchy | ⚠️ | H1 followed by H4, H3, H2 — skips levels |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | AvatarImage has alt attributes |
| Rendering | ❌ | CSR only — no content for crawlers |
Issues:
- All meta tags are generic defaults
- No canonical URL, no structured data
- Heading hierarchy broken (H1 → H4)
- CSR with no pre-rendered content
Recommendations:
- Add custom title: "100% Club - Perfect Hit Rate Player Props | StatsHub" (~55 chars)
- Add page-specific meta description
- Add canonical:
https://statshub.com/100-club - Fix heading hierarchy: H1 → H2 → H3
- Add WebPage JSON-LD
/favourites
File: src/pages/favourites.tsx | Rendering: SSR (empty props)
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | Generic default |
| Meta Description | ⚠️ | Generic default |
| Open Graph Tags | ⚠️ | Two meta tag components (MetaTags + EnhancedMetaTags) may conflict |
| Twitter Card Tags | ⚠️ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "MY FAVORITES" / "Sign in to view favorites" |
| Heading Hierarchy | ⚠️ | Acceptable but user-specific, not SEO-oriented |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | Alt attributes present |
| Rendering | ⚠️ | SSR returns empty props — effectively CSR |
Issues:
- Auth-gated page with generic meta tags
- Two meta tag components may cause duplicate tags
- getServerSideProps returns empty props
Recommendations:
- Add
<meta name="robots" content="noindex, nofollow" />— this is a user-private page - Remove duplicate MetaTags + EnhancedMetaTags — use one approach
/features
File: src/pages/features.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Football Stats Tools & player stats features | StatsHub PropHunter + More" (74 chars — too long) |
| Meta Description | ✅ | Present, ~155 chars, keyword-rich |
| Open Graph Tags | ⚠️ | Missing og and og |
| Twitter Card Tags | ⚠️ | Missing twitter and twitter |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "Powerful Football Analytics Features" |
| Heading Hierarchy | ✅ | Good: H1 → H2 → H3 |
| Structured Data | ✅ | WebPage + SoftwareApplication with featureList |
| Image Alt Text | ⚠️ | N/A — uses Lucide icons |
| Rendering | ⚠️ | CSR — entirely static content, should be SSG |
Issues:
- Title too long (74 chars)
- Missing og, og, twitter, twitter
- No canonical URL
- Static content rendered client-side
Recommendations:
- Shorten title to ~55 chars: "Football Analytics Features & Tools | StatsHub"
- Add missing OG/Twitter tags
- Add canonical:
https://statshub.com/features - Add getStaticProps for SSG
/free-tools
File: src/pages/free-tools.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | "Free Football Betting Tools & Analytics | StatsHub" (51 chars) |
| Meta Description | ⚠️ | Contains ' HTML entity that renders literally |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ⚠️ | Missing twitter |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/free-tools |
| H1 Tag | ✅ | "Free Football Betting Tools" |
| Heading Hierarchy | ✅ | Good: H1 → H2 → H3 → H4 |
| Structured Data | ✅ | WebPage + BreadcrumbList |
| Image Alt Text | ⚠️ | N/A — Lucide icons |
| Rendering | ⚠️ | CSR — static content ideal for SSG |
Issues:
'in meta description won't render as apostrophe- Missing twitter
- Static content should use SSG
Recommendations:
- Fix
'→'in meta description - Add twitter
- Add getStaticProps for SSG
/lineup-bot
File: src/pages/lineup-bot.tsx | Rendering: SSG
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | "Lineup Hunter AI - Premium Betting Intelligence | Statshub" (60 chars) |
| Meta Description | ⚠️ | Present but only 112 chars — under 150 target |
| Open Graph Tags | ⚠️ | Missing og, og, og |
| Twitter Card Tags | ❌ | No Twitter Card tags at all |
| Canonical URL | ⚠️ | Points to statshub.io instead of statshub.com — wrong domain |
| H1 Tag | ✅ | "Detect Lineup Changes. Exploit Mispriced Odds." |
| Heading Hierarchy | ✅ | Good: H1 → H2 → H3 |
| Structured Data | ❌ | None |
| Image Alt Text | ⚠️ | No images on page |
| Rendering | ✅ | SSG via getStaticProps |
Issues:
- Canonical URL uses wrong domain (
statshub.iovsstatshub.com) - No Twitter Card tags
- Missing og, og, og
- No structured data
- Brand inconsistency: "Statshub" vs "StatsHub"
Recommendations:
- Fix canonical to
https://statshub.com/lineup-bot - Add Twitter Card tags
- Add missing OG tags
- Add Product or Service structured data
- Expand meta description to 150-160 chars
- Standardize brand to "StatsHub"
/odds-converter
File: src/pages/odds-converter.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Odds Converter | Convert Fractional, Decimal & American Odds Instantly" (72 chars — over 60) |
| Meta Description | ✅ | ~155 chars, keyword-rich |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ⚠️ | Missing twitter |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/odds-converter |
| H1 Tag | ✅ | "Odds Converter" |
| Heading Hierarchy | ✅ | Excellent: H1 → H2 → H3 |
| Structured Data | ✅ | Excellent — WebPage, FAQPage, BreadcrumbList, VideoObject |
| Image Alt Text | ⚠️ | YouTube iframe has title but no img tags |
| Rendering | ⚠️ | CSR — static content ideal for SSG |
Issues:
- Title 72 chars — will be truncated
- Missing twitter
'HTML entity in FAQ JSON-LD- Static content should use SSG
Recommendations:
- Shorten title to ~60 chars
- Add twitter
- Fix
'in FAQ data - Add getStaticProps — best candidate for SSG template
/outliers
File: src/pages/outliers.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | Generic default — not specific to outliers |
| Meta Description | ⚠️ | Generic default |
| Open Graph Tags | ⚠️ | Generic, og points to homepage |
| Twitter Card Tags | ⚠️ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "Betting Market Outliers" |
| Heading Hierarchy | ⚠️ | Only H1 — no H2 or H3 subheadings |
| Structured Data | ❌ | None |
| Image Alt Text | ⚠️ | No images |
| Rendering | ❌ | CSR only — crawlers see empty page |
Issues:
- All meta tags generic, no canonical, no structured data
- No heading hierarchy below H1
- CSR with no content for crawlers
Recommendations:
- Add custom title: "Betting Market Outliers - Odds Discrepancy Finder | StatsHub"
- Add page-specific meta description
- Add canonical:
https://statshub.com/outliers - Add WebPage JSON-LD
- Add H2 section headings
/player-stats
File: src/pages/player-stats.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Football Player Stats -- xG, Shots, Tackles, Shots On Target & More | StatsHub" (74 chars — too long) |
| Meta Description | ✅ | 156 chars, compelling and keyword-rich |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ✅ | Full suite present |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/player-stats |
| H1 Tag | ✅ | "Explore Player Stats in One Place" |
| Heading Hierarchy | ✅ | Logical: H1 → H2 → H3 |
| Structured Data | ✅ | WebPage + BreadcrumbList + FAQPage (5 questions) |
| Image Alt Text | ⚠️ | No images — emoji icons used |
| Rendering | ⚠️ | CSR — static content should use SSG |
Issues:
- Title 74 chars — will be truncated
- Duplicate MetaTags component alongside custom Head tags
- Static content using CSR
Recommendations:
- Shorten title to ~58 chars
- Remove MetaTags component (custom Head already defines everything)
- Add getStaticProps for SSG
/prop-screener
File: src/pages/prop-screener.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | No title — only generic MetaTags default |
| Meta Description | ❌ | No page-specific description |
| Open Graph Tags | ❌ | Generic defaults, og points to homepage |
| Twitter Card Tags | ❌ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "PROP SCREENER" |
| Heading Hierarchy | ⚠️ | H1 present, no H2s, H3 used for empty states |
| Structured Data | ❌ | None |
| Image Alt Text | ⚠️ | No images |
| Rendering | ⚠️ | CSR — acceptable for dynamic tool but shell could be SSR |
Issues:
- Missing all page-specific SEO: title, description, canonical, OG, Twitter, JSON-LD
Recommendations:
- Add title: "Prop Screener - Filter Player Props by Hit Rate & Odds | StatsHub"
- Add meta description targeting "prop screener", "player props"
- Add canonical:
https://statshub.com/prop-screener - Add WebApplication JSON-LD
/prophunter
File: src/pages/prophunter.tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "PropHunter -- Find Value in Player Props Using Football player Stats | StatsHub" (82 chars — too long) |
| Meta Description | ⚠️ | 176 chars — over 160 target |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ✅ | Full suite present |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/prophunter |
| H1 Tag | ✅ | "PROP HUNTER" |
| Heading Hierarchy | ⚠️ | H1 only — no H2/H3 in page content |
| Structured Data | ❌ | None |
| Image Alt Text | ⚠️ | No standard images visible |
| Rendering | ✅ | SSR via GetServerSideProps |
Issues:
- Title 82 chars — too long
- Meta description 176 chars — too long
- "Football player Stats" should be "Football Player Stats" (capitalization)
- No structured data
Recommendations:
- Shorten title to ~60 chars
- Trim description to 155-160 chars
- Fix capitalization
- Add WebApplication JSON-LD
/referees
File: src/pages/referees.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | "Football Referee Stats & Card Statistics | StatsHub" (51 chars) |
| Meta Description | ✅ | 161 chars — just 1 char over ideal |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ✅ | Full suite present |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/referees |
| H1 Tag | ✅ | "Referee Stats" |
| Heading Hierarchy | ✅ | H1 → H2 (FAQ) with accordion items |
| Structured Data | ✅ | FAQPage with 8 questions |
| Image Alt Text | ✅ | alt={referee.name} on all avatars |
| Rendering | ⚠️ | CSR — data fetched via SWR |
Issues:
- Description 1 char over 160 limit
- CSR means referee data table not visible to crawlers on first load
Recommendations:
- Trim description by 1-2 chars
- Consider SSR for initial data table rendering
- Add WebPage JSON-LD alongside FAQPage
/stats-explained
File: src/pages/stats-explained.tsx | Rendering: SSG (ISR, daily)
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Football Stats Explained | Complete 2025 Guide to Player Stats & Football Statistics - StatsHub" (94 chars — very long) |
| Meta Description | ✅ | 145 chars, keyword-rich |
| Open Graph Tags | ✅ | Full suite with image dimensions and alt text |
| Twitter Card Tags | ✅ | Full suite with image alt |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/stats-explained |
| H1 Tag | ✅ | "Football Stats Explained" |
| Heading Hierarchy | ✅ | Excellent: H1 → H2 → H3 → H4 |
| Structured Data | ✅ | Article + FAQPage — both well-structured |
| Image Alt Text | ⚠️ | No images — emoji icons |
| Rendering | ✅ | SSG with ISR (revalidate: 86400) |
Issues:
- Title 94 chars — far too long
- Contains "2025" which is now outdated (January 2026)
Recommendations:
- Shorten title to ~60 chars: "Football Stats Explained - xG, Passes & More | StatsHub"
- Update or remove the year reference
- Best-optimized page — use as template for others
/team-screener
File: src/pages/team-screener.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | No title — generic MetaTags default |
| Meta Description | ❌ | No page-specific description |
| Open Graph Tags | ❌ | Generic defaults |
| Twitter Card Tags | ❌ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "TEAM SCREENER" |
| Heading Hierarchy | ⚠️ | H1 → H3 (no H2) |
| Structured Data | ❌ | None |
| Image Alt Text | ⚠️ | No images |
| Rendering | ⚠️ | CSR |
Issues:
- Missing all page-specific SEO tags
Recommendations:
- Add title: "Team Screener - Filter Team Props by Odds & Hit Rates | StatsHub"
- Add meta description, canonical, OG tags, Twitter cards, JSON-LD
/team-stats
File: src/pages/team-stats.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Football Stats | Soccer Statistics | Form Tables | StatsHub" (58 chars) — doesn't mention "Team Stats" |
| Meta Description | ⚠️ | 133 chars — under 150 target |
| Open Graph Tags | ✅ | Full suite present |
| Twitter Card Tags | ✅ | Full suite present |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/team-stats |
| H1 Tag | ✅ | "Comprehensive Team Stats" |
| Heading Hierarchy | ✅ | Excellent: H1 → H2 → H3 |
| Structured Data | ✅ | WebPage + BreadcrumbList + FAQPage |
| Image Alt Text | ⚠️ | No images — emoji icons |
| Rendering | ⚠️ | CSR — static content should be SSG |
Issues:
- Title doesn't mention "Team Stats" which is the page focus
- Description too short (133 chars)
- Duplicate MetaTags component
- Static content using CSR
Recommendations:
- Update title: "Football Team Stats - Goals, xG, Shots & More | StatsHub"
- Expand description to 150-160 chars
- Remove MetaTags component
- Add getStaticProps for SSG
/value-bets
File: src/pages/value-bets.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | No title — generic MetaTags default |
| Meta Description | ❌ | No page-specific description |
| Open Graph Tags | ❌ | Generic defaults |
| Twitter Card Tags | ❌ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ✅ | "Value Bets" |
| Heading Hierarchy | ⚠️ | H1 only — no semantic sub-sections |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | alt={source} and alt={bet.source} present |
| Rendering | ⚠️ | CSR |
Issues:
- Missing all page-specific SEO tags
Recommendations:
- Add title: "Value Bets - Football Betting Edge Finder | StatsHub"
- Add meta description, canonical, OG tags, Twitter cards, JSON-LD
/404
File: src/pages/404.tsx | Rendering: SSG (Next.js default)
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | No Head component at all |
| Meta Description | ❌ | None |
| Open Graph Tags | ❌ | None |
| Twitter Card Tags | ❌ | None |
| Canonical URL | ❌ | N/A for 404 |
| H1 Tag | ✅ | "404 - Page Not Found" |
| Heading Hierarchy | ✅ | Single H1 — appropriate for 404 |
| Structured Data | ❌ | N/A for 404 |
| Image Alt Text | ⚠️ | No images |
| Rendering | ✅ | SSG (default for 404) |
Issues:
- No
<Head>— browser tab shows default/empty title - No
<meta name="robots" content="noindex">to prevent indexing
Recommendations:
- Add
<Head>with title: "404 - Page Not Found | StatsHub" - Add
<meta name="robots" content="noindex, nofollow" />
Index / Landing Pages
/blog
File: src/pages/blog/index.tsx | Rendering: SSG (ISR, revalidate: 60s)
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Football Blog | Expert Analysis, Player Stats & Football Insights - StatsHub" (74 chars — over 60) |
| Meta Description | ⚠️ | 183 chars — over 160 target |
| Open Graph Tags | ✅ | Full suite with image dimensions and alt text |
| Twitter Card Tags | ✅ | Full suite with image alt |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/blog |
| H1 Tag | ✅ | "Football Blog & Expert Analysis" |
| Heading Hierarchy | ✅ | H1 → H2 → H3 |
| Structured Data | ✅ | Blog with BlogPosting items + FAQPage |
| Image Alt Text | ⚠️ | Depends on CardArticleUnified component |
| Rendering | ✅ | SSG with ISR |
Issues:
- Title 74 chars — will be truncated
- Description 183 chars — over limit
- Generic og
Recommendations:
- Shorten title to ~55 chars
- Trim description to 155 chars
- Create unique blog-specific og
/betting-academy
File: src/pages/betting-academy/index.tsx | Rendering: SSG (ISR, revalidate: 60s)
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | "Betting Academy | Expert Betting Guides & Strategies - StatsHub" (65 chars — slightly over) |
| Meta Description | ⚠️ | 169 chars — slightly over 160 target |
| Open Graph Tags | ✅ | Full suite with dimensions |
| Twitter Card Tags | ✅ | Full suite with alt |
| Canonical URL | ✅ | Self-referencing: https://statshub.com/betting-academy |
| H1 Tag | ✅ | "StatsHub Betting Academy" |
| Heading Hierarchy | ✅ | H1 → H2 → H3 |
| Structured Data | ✅ | EducationalOrganization + FAQPage |
| Image Alt Text | ⚠️ | Depends on CardArticleUnified component |
| Rendering | ✅ | SSG with ISR |
Issues:
- Title 65 chars — slightly over
- Description 169 chars — slightly over
- EducationalOrganization schema uses
courseMode/teacheswhich belong to Course schema
Recommendations:
- Trim title to ~55 chars
- Trim description to 155 chars
- Fix JSON-LD type or remove non-standard properties
- Create unique og
/league
File: src/pages/league/index.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | No Head component at all |
| Meta Description | ❌ | None |
| Open Graph Tags | ❌ | None |
| Twitter Card Tags | ❌ | None |
| Canonical URL | ❌ | None |
| H1 Tag | ✅ | "Football Leagues" |
| Heading Hierarchy | ✅ | H1 → H2 ("Popular Leagues", "All Leagues") → H3 |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | alt={league.name} present |
| Rendering | ❌ | CSR via useSWR — crawlers see empty shell |
Issues:
- Most SEO-deficient page in the audit — no Head, no meta tags, no structured data, CSR only
- League list invisible to crawlers
Recommendations:
- Add full
<Head>block with title, description, OG tags, Twitter cards - Add canonical:
https://statshub.com/league - Add getStaticProps or getServerSideProps to pre-render league list
- Add ItemList or CollectionPage JSON-LD
- Critical fix — this is a high-value SEO page
/basketball
File: src/pages/basketball/index.tsx | Rendering: CSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | "Basketball Games & Scores | StatsHub" (43 chars) |
| Meta Description | ⚠️ | 119 chars — under 150 target |
| Open Graph Tags | ⚠️ | Missing og, og, og |
| Twitter Card Tags | ❌ | None present |
| Canonical URL | ✅ | https://statshub.com/basketball |
| H1 Tag | ✅ | "Today's Basketball Games & Scores" |
| Heading Hierarchy | ✅ | H1 → H2 |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | Alt attributes on team/tournament images |
| Rendering | ❌ | CSR via useSWR — game data invisible to crawlers |
Issues:
- Short meta description (119 chars)
- Missing OG tags (og, og, og)
- No Twitter Card tags
- No structured data
- CSR only
Recommendations:
- Expand description to 150-160 chars
- Add missing OG and Twitter Card tags
- Add SportsEvent or WebPage JSON-LD
- Consider SSR for game data
Dynamic Route Templates
/betting-academy/[slug]
File: src/pages/betting-academy/[slug].tsx | Rendering: SSG (ISR, revalidate: 60s)
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {post.title} - StatsHub Betting Academy |
| Meta Description | ✅ | Dynamic: uses post.metaDescription from CMS |
| Open Graph Tags | ✅ | All present with dynamic data, conditional cover image |
| Twitter Card Tags | ✅ | summary_large_image with dynamic data |
| Canonical URL | ✅ | Dynamic: https://statshub.com/betting-academy/${post.slug} |
| H1 Tag | ✅ | Dynamic from post.title via BlogHero |
| Heading Hierarchy | ✅ | H1 in hero, H2/H3 via markdown renderer |
| Structured Data | ✅ | Article schema with dynamic headline, dates, author, publisher |
| Image Alt Text | ⚠️ | Depends on BlogHero component |
| Rendering | ✅ | SSG with ISR |
| Dynamic SEO | ✅ | All meta tags fully data-driven |
Issues: None significant. Best-optimized page in the codebase.
Recommendations:
- Verify BlogHero applies alt text to cover image
- Consider adding
twitter:sitehandle
/fixture/[fixtureSlug]/[fixtureId]
File: src/pages/fixture/[fixtureSlug]/[fixtureId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {team1} vs {team2} {date} | Live Stats, H2H, Lineups & Analysis |
| Meta Description | ✅ | Dynamic with both team names and date |
| Open Graph Tags | ✅ | Full suite via EnhancedMetaTags |
| Twitter Card Tags | ✅ | summary_large_image with dynamic data |
| Canonical URL | ✅ | Dynamic: https://statshub.com/fixture/{slug}/{id} |
| H1 Tag | ⚠️ | No explicit H1 — relies on FixtureHeader component |
| Heading Hierarchy | ⚠️ | No visible headings in page file |
| Structured Data | ✅ | SportsEvent with dynamic teams, date, venue, league |
| Image Alt Text | ✅ | Dynamic alt text on team avatars |
| Rendering | ✅ | SSR |
| Dynamic SEO | ✅ | All meta tags use dynamic fixture data |
Issues:
- og defaults to generic
og-image.png - No explicit H1 visible in page file
Recommendations:
- Verify FixtureHeader renders an H1
- Consider dynamic OG image generation per fixture
/player/[playerSlug]/[playerId]
File: src/pages/player/[playerSlug]/[playerId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {playerName} Stats | {teamName} {position} Performance & Analysis |
| Meta Description | ✅ | Dynamic with player and team names |
| Open Graph Tags | ✅ | Full suite via EnhancedMetaTags |
| Twitter Card Tags | ✅ | summary_large_image with dynamic data |
| Canonical URL | ✅ | Dynamic: https://statshub.com/player/{slug}/{id} |
| H1 Tag | ⚠️ | No explicit H1 — relies on PlayerHeader |
| Heading Hierarchy | ⚠️ | Depends on child components |
| Structured Data | ✅ | Person schema with name, team, position, nationality, age, jersey |
| Image Alt Text | ✅ | Alt attributes on player/team avatars |
| Rendering | ✅ | SSR |
| Dynamic SEO | ⚠️ | Stats not passed to EnhancedMetaTags — description uses generic fallback |
Issues:
- Player statistics not passed to meta tags — description says "detailed stats" instead of actual numbers
- og defaults to generic
- No explicit H1
Recommendations:
- Pass stats prop to EnhancedMetaTags from getServerSideProps
- Verify PlayerHeader renders H1
- Consider player-specific OG image
/team/[teamSlug]/[teamId]
File: src/pages/team/[teamSlug]/[teamId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {teamName} Stats {season} | Squad, Fixtures, Results & Analysis |
| Meta Description | ✅ | Dynamic with team name |
| Open Graph Tags | ✅ | Full suite via EnhancedMetaTags |
| Twitter Card Tags | ✅ | summary_large_image with dynamic data |
| Canonical URL | ✅ | Dynamic: https://statshub.com/team/{slug}/{id} |
| H1 Tag | ⚠️ | No explicit H1 — relies on TeamHeader |
| Heading Hierarchy | ⚠️ | Depends on child components |
| Structured Data | ✅ | SportsTeam with name, league, venue |
| Image Alt Text | ✅ | Alt attributes present |
| Rendering | ✅ | SSR |
| Dynamic SEO | ⚠️ | Season prop depends on client-side SWR — undefined at SSR time |
Issues:
seasondepends on client-side data — crawlers see no season in title- og defaults to generic
Recommendations:
- Fetch season in getServerSideProps
- Verify TeamHeader renders H1
- Consider team badge as OG image
/league/[leagueSlug]/[leagueId]
File: src/pages/league/[leagueSlug]/[leagueId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {leagueName} {season} Table, Fixtures & Results {country} | {teamCount} Teams | StatsHub |
| Meta Description | ✅ | Highly dynamic: league name, season, team count, champion, match count |
| Open Graph Tags | ✅ | Full suite via EnhancedMetaTags |
| Twitter Card Tags | ✅ | Enhanced with twitter/data1 for top scorer |
| Canonical URL | ✅ | Dynamic: https://statshub.com/league/{slug}/{id} |
| H1 Tag | ⚠️ | No explicit H1 — relies on LeagueHeader |
| Heading Hierarchy | ⚠️ | Depends on child components |
| Structured Data | ✅ | Excellent: SportsOrganization, SportsCompetition, BreadcrumbList, FAQPage |
| Image Alt Text | ⚠️ | In child components |
| Rendering | ✅ | SSR |
| Dynamic SEO | ⚠️ | Critical: season, teamCount, champion, leagueStats all from client-side SWR — absent in SSR HTML |
Issues:
- High impact: All rich meta tag data is invisible to crawlers because it depends on client-side SWR
lastUpdatedruns on client, not server- og defaults to generic
Recommendations:
- Fetch standings data in getServerSideProps so teamCount, champion, season are in SSR HTML
- This is the highest-impact fix — excellent meta logic but invisible to crawlers
- Verify LeagueHeader renders H1
/referee/[refereeSlug]
File: src/pages/referee/[refereeSlug].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ✅ | Dynamic: {refereeName} Referee Stats | {yellowCards/game} | Match History & Analysis |
| Meta Description | ✅ | Dynamic: referee name, games officiated, cards per game |
| Open Graph Tags | ✅ | Full suite with referee-specific image |
| Twitter Card Tags | ✅ | summary_large_image with dynamic data |
| Canonical URL | ✅ | Dynamic: https://statshub.com/referee/{slug} |
| H1 Tag | ⚠️ | No explicit H1 — relies on RefereeHeader |
| Heading Hierarchy | ✅ | H2 → H3 → H4 in SEO content section |
| Structured Data | ✅ | Person, BreadcrumbList, FAQPage — all fully dynamic |
| Image Alt Text | ✅ | Alt attributes with tournament names |
| Rendering | ✅ | SSR with all key data prefetched |
| Dynamic SEO | ✅ | All meta data from SSR props — most complete dynamic page |
Issues:
- No explicit H1 visible (relies on RefereeHeader)
- Referee image may 404 if not uploaded
Recommendations:
- Verify RefereeHeader renders H1
- Add fallback for og
/basketball/fixture/[fixtureSlug]/[fixtureId]
File: src/pages/basketball/fixture/[fixtureSlug]/[fixtureId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | Generic "Free Football & Player Stats" — not basketball, not dynamic |
| Meta Description | ❌ | Generic football description |
| Open Graph Tags | ❌ | Generic, og hardcoded to homepage |
| Twitter Card Tags | ❌ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ⚠️ | Relies on BasketballFixtureHeader |
| Heading Hierarchy | ⚠️ | No headings in page file |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | Team alt text present |
| Rendering | ✅ | SSR |
| Dynamic SEO | ❌ | No dynamic SEO whatsoever |
Issues:
- Zero SEO — all meta tags are generic football defaults on a basketball page
Recommendations:
- Replace MetaTags with EnhancedMetaTags using basketball fixture data
- Add StructuredData with sportsEvent type
- Add canonical URL
/basketball/player/[playerSlug]/[playerId]
File: src/pages/basketball/player/[playerSlug]/[playerId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ⚠️ | Dynamic but generic: {playerName} - Player Stats |
| Meta Description | ⚠️ | References "goals, assists, shots" — football terminology on basketball page |
| Open Graph Tags | ⚠️ | Dynamic title but og hardcoded to homepage |
| Twitter Card Tags | ⚠️ | Dynamic title but generic image |
| Canonical URL | ❌ | Not set |
| H1 Tag | ⚠️ | Relies on BasketballPlayerHeader |
| Heading Hierarchy | ⚠️ | No headings in page file |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | Alt text present |
| Rendering | ✅ | SSR with slug redirect |
| Dynamic SEO | ⚠️ | Partially dynamic but uses wrong sport terminology |
Issues:
- Meta description uses football terms ("goals, assists, shots") instead of basketball ("points, rebounds, assists")
- No canonical, no structured data, og wrong
Recommendations:
- Create basketball-specific meta component with basketball terminology
- Add canonical:
https://statshub.com/basketball/player/{slug}/{id} - Add Person structured data
- Fix og
/basketball/team/[teamSlug]/[teamId]
File: src/pages/basketball/team/[teamSlug]/[teamId].tsx | Rendering: SSR
| Check | Status | Details |
|---|---|---|
| Title Tag | ❌ | Generic "Free Football & Player Stats" — not basketball, not dynamic |
| Meta Description | ❌ | Generic football description |
| Open Graph Tags | ❌ | Generic, og hardcoded to homepage |
| Twitter Card Tags | ❌ | Generic defaults |
| Canonical URL | ❌ | Not set |
| H1 Tag | ⚠️ | Relies on BasketballTeamHeader |
| Heading Hierarchy | ⚠️ | No headings in page file |
| Structured Data | ❌ | None |
| Image Alt Text | ✅ | Team alt text present |
| Rendering | ✅ | SSR |
| Dynamic SEO | ❌ | No dynamic SEO whatsoever |
Issues:
- Zero SEO — generic football defaults on a basketball page
Recommendations:
- Replace MetaTags with EnhancedMetaTags using basketball team data
- Add SportsTeam structured data
- Add canonical:
https://statshub.com/basketball/team/{slug}/{id}
Overall Scorecard
| Page | Title | Desc | OG | Canonical | H1 | JSON-LD | Rendering | Grade | |
|---|---|---|---|---|---|---|---|---|---|
| / | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | B+ |
| /100-club | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ✅ | ❌ | ❌ | D |
| /favourites | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ✅ | ❌ | ⚠️ | D |
| /features | ⚠️ | ✅ | ⚠️ | ⚠️ | ❌ | ✅ | ✅ | ⚠️ | C+ |
| /free-tools | ✅ | ⚠️ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | B |
| /lineup-bot | ✅ | ⚠️ | ⚠️ | ❌ | ⚠️ | ✅ | ❌ | ✅ | C |
| /odds-converter | ⚠️ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | B+ |
| /outliers | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ✅ | ❌ | ❌ | D |
| /player-stats | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | B+ |
| /prop-screener | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ⚠️ | F |
| /prophunter | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | B- |
| /referees | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | A- |
| /stats-explained | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | A- |
| /team-screener | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ⚠️ | F |
| /team-stats | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | B |
| /value-bets | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ⚠️ | F |
| /404 | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ | D |
| /blog | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | A- |
| /betting-academy | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | A- |
| /league | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | F |
| /basketball | ✅ | ⚠️ | ⚠️ | ❌ | ✅ | ✅ | ❌ | ❌ | C- |
| /betting-academy/[slug] | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | A |
| /fixture/[slug]/[id] | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | A- |
| /player/[slug]/[id] | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | B+ |
| /team/[slug]/[id] | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | B+ |
| /league/[slug]/[id] | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | B |
| /referee/[slug] | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ✅ | A |
| /basketball/fixture | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ✅ | F |
| /basketball/player | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ❌ | ⚠️ | ❌ | ✅ | D |
| /basketball/team | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ | ❌ | ✅ | F |
Prioritized Action Plan
Critical Fixes (Blocking Ranking)
| # | Fix | Pages Affected | Impact |
|---|---|---|---|
| 1 | Add page-specific <Head> with title, description, canonical, OG, Twitter to F-graded pages | /prop-screener, /team-screener, /value-bets, /league | High — these pages are invisible to search engines |
| 2 | Replace generic football MetaTags with basketball-specific EnhancedMetaTags on all basketball dynamic routes | /basketball/fixture, /basketball/player, /basketball/team | High — wrong sport terminology, no dynamic data |
| 3 | Fix MetaTags component og:url bug — hardcoded to homepage for all "default" pages | 8+ pages using MetaTags pageType="default" | High — incorrect OG URL across the site |
| 4 | Fix /lineup-bot canonical URL from statshub.io to statshub.com | /lineup-bot | High — pointing crawlers to wrong domain |
High-Impact Improvements
| # | Fix | Pages Affected | Impact |
|---|---|---|---|
| 5 | Move league standings/season data fetch into getServerSideProps | /league/[slug]/[id] | High — rich meta tags invisible to crawlers |
| 6 | Pass player stats to EnhancedMetaTags from getServerSideProps | /player/[slug]/[id] | Medium — generic description instead of actual stats |
| 7 | Fetch season in getServerSideProps for team pages | /team/[slug]/[id] | Medium — season missing from title at SSR |
| 8 | Audit all Header components for H1 rendering | All dynamic route pages | Medium — may be missing H1 tags |
Quick Wins (Easy, Immediate Benefit)
| # | Fix | Pages Affected | Impact |
|---|---|---|---|
| 9 | Add <meta name="robots" content="noindex" /> to non-indexable pages | /404, /favourites | Low — prevents wasted crawl budget |
| 10 | Fix ' HTML entities in meta descriptions and JSON-LD | /free-tools, /odds-converter | Low — broken characters in SERPs |
| 11 | Shorten over-length title tags to 50-60 chars | /, /blog, /betting-academy, /features, /player-stats, /prophunter, /stats-explained, /odds-converter | Medium — prevents SERP truncation |
| 12 | Add missing twitter:image tags | /features, /free-tools, /odds-converter, /lineup-bot | Low — missing social sharing image |
| 13 | Update "2025" to "2026" in /stats-explained title | /stats-explained | Low — outdated year reference |
Long-Term Recommendations
| # | Fix | Pages Affected | Impact |
|---|---|---|---|
| 14 | Add getStaticProps to all purely static content pages for SSG | /features, /free-tools, /odds-converter, /player-stats, /team-stats | Medium — faster loads, better crawlability |
| 15 | Create a global DefaultSeo component with fallback meta tags | All pages | Medium — prevents pages from having zero SEO |
| 16 | Migrate all remaining MetaTags usage to EnhancedMetaTags | All pages still using MetaTags | Medium — EnhancedMetaTags has dynamic URL support |
| 17 | Generate dynamic OG images per fixture/player/team/league | All dynamic routes | Low — improved social sharing CTR |
| 18 | Add WebApplication JSON-LD to tool pages | /prop-screener, /team-screener, /value-bets, /100-club, /outliers | Low — rich search results for tools |
| 19 | Use /odds-converter and /stats-explained as SEO templates for other pages | All pages | — Best-practice reference pages |
Template Pages (Use as Reference)
The following pages represent best practices and should be used as templates when fixing other pages:
/betting-academy/[slug]— Grade A — Perfect dynamic SSG page with full meta tags, structured data, and data-driven SEO/referee/[refereeSlug]— Grade A — Perfect dynamic SSR page with full meta tags, multiple JSON-LD schemas, and all data from SSR props/stats-explained— Grade A- — Best static page with Article + FAQPage schemas, ISR, and comprehensive meta tags/odds-converter— Grade B+ — Best tool page with 4 JSON-LD schemas (WebPage, FAQPage, BreadcrumbList, VideoObject)