Audit websites for SEO issues and optimize content for search engine visibility.
Audit websites for technical SEO issues, analyze on-page optimization, and provide actionable recommendations to improve search engine visibility and rankings.
Before anything else, determine whether the site is a React/Vue/Angular SPA or server-side rendered.
<title> and <meta> in index.html are all Google sees.How to detect: curl -s https://domain.com/some-page | grep "<h1" — if no H1 is in the curl output but is visible in the browser, the page is SPA-rendered and invisible to Googlebot.
For SPA+SSR hybrids: Audit only the SSR pages deeply. Note which routes are SPA (and therefore invisible) without alarming the user — authenticated dashboards being SPA is expected and fine.
Robots.txt:
/robots.txt if no Express route handles it. Always curl -s /robots.txt | head -5 and confirm the output is User-agent: plain text, not <!DOCTYPE html>.XML Sitemap:
/sitemap.xml. curl /sitemap.xml and confirm the first line is <?xml version=. If it returns <!DOCTYPE html>, the sitemap route is missing and the SPA is serving as fallback.BANK_GUIDES, GLOSSARY_TERMS). This way, adding new entries to the arrays automatically updates the sitemap — no manual maintenance needed.Site Architecture:
Index Status:
Indexation Issues:
Canonicalization:
Core Web Vitals (2025-2026):
Speed Factors:
Font Bundle Audit — Common Performance Killer:
Check client/index.html (the actual source file, not just what Googlebot sees) for bloated Google Fonts requests. A single <link href="fonts.googleapis.com/css2?family=Inter&family=Roboto&family=Poppins&..."> loading 10+ font families is a render-blocking LCP killer.
Correct async font loading pattern:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"></noscript>
Apply this pattern to both client/index.html (SPA) and any SSR shared shell functions.
Mobile-Friendliness:
Security:
URL Structure:
Note: Google now excludes pages returning non-200 status codes (4xx, 5xx) from the rendering queue entirely — critical for SPAs.
Title Tags:
| separator (not - or —)↔, →, ©). These look odd in SERPs and can be keyword-unfriendly. Use plain text equivalents.Meta Descriptions:
Heading Structure:
Content Optimization:
Image Optimization:
Internal Linking:
Keyword Targeting (per page):
Experience: First-hand experience demonstrated, original insights/data, real examples Expertise: Author credentials visible, accurate and detailed information, properly sourced claims Authoritativeness: Recognized in the space, cited by others, industry credentials Trustworthiness: Accurate information, transparent about business, contact info available, privacy policy, HTTPS
Schema Markup:
Every SSR page should have the appropriate schema type. Use an array-based approach in your shared shell function so each schema is its own <script type="application/ld+json"> block — never concatenate JSON strings and inject them inside an existing <script> tag, as this creates malformed HTML with nested script tags.
Correct pattern (Express/Node SSR shell function):
// Accept an array of schema JSON strings
schemaJsons?: string[];
// Render each as its own script block
allSchemas.map(s => `<script type="application/ld+json">${s}</script>`).join("\n")
Always-present schemas (add to shared shell):
WebSite + Organization @graph — entity identity for Google's Knowledge Graph, sitelinks signalsBreadcrumbList — add to every page that has visible breadcrumb navigation, even if the page already has another schema type (HowTo, DefinedTerm, etc.)Page-specific schemas:
datePublished, dateModified)Always include datePublished and dateModified in schema — freshness signals help maintain rankings on competitive queries.
OG Image requirement: og:image requires an actual hosted image URL (1200×630px). Base64-embedded images cannot be OG images. If no hosted image exists, note it as a blocker for social sharing and skip the og:image tag rather than pointing to a non-existent URL.
Minimum social meta set (every page):
<meta property="og:title" content="..." />
<meta property="og:description" content="..." />
<meta property="og:url" content="..." />
<meta property="og:type" content="website" />
<meta property="og:locale" content="..." /> <!-- e.g., es_DO, en_US -->
<meta property="og:site_name" content="..." />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="..." />
<meta name="twitter:description" content="..." />
<meta name="twitter:site" content="@handle" />
Schema Markup Detection Warning: webFetch and curl cannot reliably detect structured data — many CMS plugins inject JSON-LD via client-side JavaScript. Never report "no schema found" based solely on webFetch. Recommend using Google Rich Results Test or browser DevTools for accurate schema verification. For SSR pages, curl is reliable.
When a site uses a shared HTML shell function (common in Express/Node SSR setups), a single change to that function fixes all pages simultaneously. This is the highest-leverage opportunity in an SSR SEO audit.
Audit the shell function for these — fixing once applies to all pages:
og:locale, og:site_name — often missing<meta name="theme-color"> — small trust/UX signalWebSite + Organization JSON-LD — missing from most sitesPer-page additions (must be done individually):
BreadcrumbList schema — specific to each page's breadcrumb pathdatePublished / dateModified — specific to each content type's schemaSaaS/Product Sites: Product pages lack content depth, blog not integrated with product pages, missing comparison/alternative pages, thin feature pages
SPA + SSR Hybrid (React/Node, Next.js, etc.):
index.html affecting landing page LCPindex.html (since that's all social crawlers see for SPA pages)E-commerce: Thin category pages, duplicate product descriptions, missing product schema, faceted navigation creating duplicates
Content/Blog Sites: Outdated content not refreshed, keyword cannibalization, no topical clustering, poor internal linking
Local Business: Inconsistent NAP, missing local schema, no Google Business Profile optimization
# SEO Audit Report: [Website]
## Executive Summary
- Overall health assessment
- Top 3-5 priority issues
- Quick wins identified
## Critical Issues (Fix Immediately)
| Issue | Page | Impact | Evidence | Fix |
|-------|------|--------|----------|-----|
## High-Impact Improvements
| Issue | Page | Impact | Evidence | Fix |
|-------|------|--------|----------|-----|
## Quick Wins
| Opportunity | Page | Potential Impact |
|------------|------|-----------------|
## Page-by-Page Analysis
### [Page URL]
- **Title**: Current | Recommended
- **Meta Description**: Current | Recommended
- **H1**: Current | Recommended
- **Content Score**: X/10
- **Issues**: [list]
## Prioritized Action Plan
1. Critical fixes (blocking indexation/ranking)
2. High-impact improvements (SSR shell function — fix once, applies to all pages)
3. Quick wins (easy, immediate benefit)
4. Long-term recommendations (OG image creation, Privacy/Terms SSR, etc.)
Free: Google Search Console, Google PageSpeed Insights, Rich Results Test (use for schema validation — it renders JavaScript), Mobile-Friendly Test, Schema Validator Paid (if available): Screaming Frog, Ahrefs / Semrush, Sitebulb
/robots.txt or /sitemap.xml. Confirm content type and first few lines