Technical SEO
Master Blueprint
101% Exhaustive · Enterprise-Grade · Framework-Agnostic
The definitive technical SEO operating standard for Senior Staff Engineers and Technical Marketing Directors. This document establishes the exact implementation contracts that govern crawl architecture, rendering pipelines, canonicalization logic, performance budgets, structured data injection, internationalization, and security hardening — for any modern web architecture.
Why Technical SEO Is an Engineering Discipline
Technical SEO is not a marketing afterthought. It is a first-class engineering concern that determines whether search engine crawlers can discover, render, evaluate, and index your content at scale. Every architectural decision — from your rendering strategy to your HTTP response headers — directly influences your Search Quality Evaluator compatibility, crawl efficiency, and ultimately your organic revenue ceiling.
This blueprint treats Google Search (Googlebot), Bing (Bingbot), and derivative bots as programmatic clients whose SLAs your infrastructure must respect. It is organized into seven architectural pillars, each with implementation contracts enforced at the code, infrastructure, and deployment levels.
Core SEO Engineering Philosophy
Crawl Architecture & Crawl Budget Optimization
robots.txt · XML Sitemaps · Budget Analysis · Faceted Navigation · Log Mining
1.1 Advanced robots.txt Directives & Edge-Case Handling
The robots.txt Specification Contract
The Robots Exclusion Protocol (REP) is a de-facto standard, not an RFC. Google's implementation diverges from Bing's in subtle ways. The critical rule: Disallow blocks crawling, but does NOT prevent indexation if a page has external links pointing to it. Use noindex in HTTP headers or meta robots for true exclusion.
Critical robots.txt Rules by Bot
| Directive | Googlebot Behavior | Bingbot Behavior | Risk Level |
|---|---|---|---|
| Disallow: / | Stops crawling all paths; won't deindex existing URLs immediately | Same | CRITICAL |
| Disallow: /*.json$ | Blocks .json file crawling (wildcard supported) | Partial support | HIGH |
| Allow: /api/public | Overrides parent Disallow | Supported | INFO |
| Crawl-delay | Ignored by Googlebot | Honored | MEDIUM |
| Sitemap: | Full support — multiple allowed | Full support | SAFE |
Production robots.txt — Full Blueprint
# ═══════════════════════════════════════════════════════════ # robots.txt — codebitbybit.com Production Configuration # Last-Modified: 2025-01-01 | Review cycle: monthly # ═══════════════════════════════════════════════════════════ # ─── GLOBAL GOOGLEBOT DIRECTIVES ─────────────────────────── User-agent: Googlebot Allow: / Disallow: /api/ # Block internal API endpoints Disallow: /admin/ # Block admin panel Disallow: /checkout/ # Block transactional funnel Disallow: /account/ # Block authenticated pages Disallow: /*?session= # Block session-ID parameterized URLs Disallow: /*?ref= # Block referral tracking params Disallow: /*?utm_ # Block UTM parameter variants Disallow: /search? # Block internal search result pages Disallow: /cdn-cgi/ # Block Cloudflare internals Disallow: /_next/ # Block Next.js build artifacts (allow below) Allow: /_next/static/ # Allow static assets for rendering Allow: /_next/image # Allow Next.js image optimizer # ─── GOOGLE IMAGE BOT ───────────────────────────────────── User-agent: Googlebot-Image Allow: / Disallow: /private-assets/ # ─── BINGBOT ────────────────────────────────────────────── User-agent: Bingbot Allow: / Disallow: /api/ Disallow: /admin/ Disallow: /checkout/ Crawl-delay: 2 # Bingbot honors this; Googlebot ignores it # ─── BLOCK AGGRESSIVE / SCRAPER BOTS ────────────────────── User-agent: AhrefsBot Disallow: / User-agent: SemrushBot Disallow: / User-agent: DotBot Disallow: / # ─── CATCH-ALL DEFAULT ──────────────────────────────────── User-agent: * Disallow: /api/ Disallow: /admin/ # ─── SITEMAP DECLARATIONS ───────────────────────────────── Sitemap: https://codebitbybit.com/sitemap-index.xml Sitemap: https://codebitbybit.com/sitemap-images.xml Sitemap: https://codebitbybit.com/sitemap-video.xml
Nginx: Serving robots.txt with Proper Cache Headers
# nginx.conf — robots.txt serving configuration location = /robots.txt { root /var/www/public; add_header Cache-Control "public, max-age=86400, s-maxage=86400"; add_header X-Robots-Tag "noindex"; # Don't index robots.txt itself log_not_found off; access_log off; }
1.2 XML Sitemap Strategy for 1M+ Dynamic URLs
Sitemap Index Architecture
A single sitemap is limited to 50,000 URLs and 50MB (uncompressed). For sites exceeding this, implement a Sitemap Index that references child sitemaps. Design child sitemaps by content type and freshness profile — not alphabetically.
Recommended Sitemap Taxonomy
- sitemap-index.xml — Root index file; lists all child sitemaps
- sitemap-pages.xml — Static/marketing pages (low volume, high priority)
- sitemap-posts-{YYYY-MM}.xml — Blog/article content bucketed by publish month
- sitemap-products-{category}.xml — Products bucketed by category (1–10K per file)
- sitemap-images.xml — Image sitemap for visual search discovery
- sitemap-video.xml — Video sitemap for VideoObject indexation
- sitemap-hreflang-{locale}.xml — Per-locale sitemaps for i18n (see Pillar 06)
Sitemap Index XML — Production Template
<?xml version="1.0" encoding="UTF-8"?> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <!-- Static pages — updated rarely --> <sitemap> <loc>https://codebitbybit.com/sitemap-pages.xml</loc> <lastmod>2025-01-01</lastmod> </sitemap> <!-- Dynamically generated per-month article sitemaps --> <sitemap> <loc>https://codebitbybit.com/sitemap-posts-2025-01.xml</loc> <lastmod>2025-01-31</lastmod> </sitemap> <!-- Product category sitemaps --> <sitemap> <loc>https://codebitbybit.com/sitemap-products-courses.xml</loc> <lastmod>2025-01-15</lastmod> </sitemap> <!-- Rich media sitemaps --> <sitemap> <loc>https://codebitbybit.com/sitemap-images.xml</loc> </sitemap> <sitemap> <loc>https://codebitbybit.com/sitemap-video.xml</loc> </sitemap> </sitemapindex>
Dynamic Sitemap Generation — Next.js App Router
// app/sitemap.ts — Next.js 14+ App Router Dynamic Sitemap // This generates sitemap.xml at build time (SSG) or on-demand (ISR) import { MetadataRoute } from 'next' import { db } from '@/lib/database' export const revalidate = 3600 // ISR: revalidate hourly export const dynamic = 'force-static' export default async function sitemap(): Promise<MetadataRoute.Sitemap> { // Fetch only URLs with indexable content (exclude drafts, private) const posts = await db.post.findMany({ where: { status: 'published', visibility: 'public' }, select: { slug: true, updatedAt: true, priority: true }, orderBy: { updatedAt: 'desc' }, }) const staticRoutes: MetadataRoute.Sitemap = [ { url: 'https://codebitbybit.com', lastModified: new Date(), changeFrequency: 'weekly', priority: 1.0, }, { url: 'https://codebitbybit.com/courses', lastModified: new Date(), changeFrequency: 'daily', priority: 0.9, }, ] const postRoutes: MetadataRoute.Sitemap = posts.map((post) => ({ url: `https://codebitbybit.com/blog/${post.slug}`, lastModified: post.updatedAt, changeFrequency: 'monthly', priority: post.priority ?? 0.7, })) return [...staticRoutes, ...postRoutes] } // ─── SEPARATE: Image Sitemap Generation ────────────────────── // app/sitemap-images/route.ts — served at /sitemap-images.xml import { NextResponse } from 'next/server' export async function GET() { const images = await db.media.findMany({ where: { type: 'IMAGE' } }) const xml = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"> ${images.map(img => ` <url> <loc>https://codebitbybit.com/${img.pageSlug}</loc> <image:image> <image:loc>${img.cdnUrl}</image:loc> <image:title>${img.altText}</image:title> </image:image> </url>`).join('')} </urlset>` return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8', 'Cache-Control': 'public, max-age=3600, s-maxage=86400', }, }) }
1.3 Crawl Budget Management
Crawl budget = crawl rate limit × crawl demand. Google allocates budget based on site authority, server response speed, and content freshness signals. Every wasted crawl on a non-indexable URL is stolen from your valuable pages.
HTTP Status Code Strictness Matrix
| Status Code | Crawl Budget Impact | Action Required |
|---|---|---|
| 200 OK | Consumed — content evaluated | Ensure only indexable pages return 200 |
| 301 Permanent | Consumed (2 requests) — passes equity | Minimize redirect chains; max 1 hop |
| 302 Temporary | Consumed — equity NOT passed | Replace with 301 for permanent moves |
| 404 Not Found | Consumed — then deprioritized | Fix broken internal links immediately |
| Soft 404 | Consumed — index bloat risk | Return 404/410 or 301 to relevant content |
| 410 Gone | Consumed — signals permanent removal | Use for intentional deindexation |
| 500/503 | Consumed — rate limit triggered | Fix immediately; repeated 5xx reduces budget |
| Redirect Loop | Consumed × N — budget hemorrhage | Audit redirect chains weekly in Screaming Frog |
1.4 Log File Analysis for Crawl Intelligence
Server access logs are your ground truth for crawl behavior. No tool approximates what the raw log gives you: exact URLs Googlebot visited, response codes, crawl frequency, and time-of-crawl patterns.
Log Parsing with GoAccess or Custom Script
# Bash: Extract Googlebot crawl activity from Nginx access.log # Outputs: URLs, status codes, and crawl timestamps grep -i "googlebot" /var/log/nginx/access.log \ | awk '{print $7, $9, $1}' \ | sort | uniq -c | sort -rn \ | head -100 \ > /tmp/googlebot-crawl-report.txt # Filter only 4xx/5xx responses (wasted crawl budget) grep -i "googlebot" /var/log/nginx/access.log \ | awk '$9 ~ /^[45]/' \ | awk '{print $9, $7}' \ | sort | uniq -c | sort -rn \ > /tmp/googlebot-errors.txt # Crawl frequency analysis per URL segment grep -i "googlebot" /var/log/nginx/access.log \ | awk '{print $7}' \ | sed 's/?.*//' \ | cut -d'/' -f1-3 \ | sort | uniq -c | sort -rn \ > /tmp/googlebot-path-frequency.txt
Key Crawl Audit Signals to Extract
- Crawl frequency delta between high-value pages vs. low-value pages (should favor high-value)
- % of crawled URLs returning non-200 status (target: <2%)
- Googlebot crawling parameterized/faceted URLs you blocked in robots.txt (means your internal links are leaking)
- Excessive crawling of paginated archives deeper than page 3 (indicates shallow indexation elsewhere)
- Bots hitting non-canonical URL variants (signals canonical implementation failure)
- Crawl rate drops (server instability perceived by Googlebot — triggers rate limiting)
Rendering Strategies & JavaScript SEO
CSR · SSR · SSG · ISR · Dynamic Rendering · DOM vs Source · Googlebot JS
2.1 SEO Implications of Every Rendering Mode
CSR — Client-Side Rendering
SEO Risk: HIGH- HTML shell served; content built by JavaScript in browser
- Googlebot queues page for second-wave rendering (WRS)
- WRS has rendering budget limits — not all pages rendered
- Metadata, canonical, hreflang unreadable until JS executes
- LCP is always script-dependent → poor CWV baseline
SSR — Server-Side Rendering
SEO Risk: LOW- Full HTML in initial response — no JS rendering required
- Googlebot processes page in first wave
- TTFB depends on server processing time
- Dynamic content rendered at request time — always fresh
- Higher origin server load vs. SSG
SSG — Static Site Generation
SEO Risk: MINIMAL- HTML pre-built at deploy time — fastest possible TTFB
- Googlebot reads full content in first wave, no WRS needed
- Edge-cached globally — sub-50ms TTFB achievable
- Stale content risk for frequently-updated pages
- Build time scales with page count (mitigate with ISR)
ISR — Incremental Static Regeneration
SEO Risk: LOW- SSG pages regenerated on-demand after revalidate TTL
- First request post-expiry serves stale; regenerates in background
- Combines SSG performance with near-SSR freshness
- On-demand revalidation via Next.js revalidatePath() API
- Potential for temporarily stale canonical/hreflang tags
Next.js Per-Route Rendering Strategy Configuration
// Next.js App Router — Explicit rendering control per route segment // Place in each route's layout.ts or page.ts // 1. FORCE STATIC (SSG) — for evergreen content export const dynamic = 'force-static' export const revalidate = false // Never regenerate automatically // 2. ISR — for semi-dynamic content (e.g. course listings) export const dynamic = 'force-static' export const revalidate = 3600 // Regenerate at most every 1 hour // 3. SSR — for user-specific or real-time content export const dynamic = 'force-dynamic' export const revalidate = 0 // No caching // 4. On-demand ISR revalidation — trigger after CMS updates // app/api/revalidate/route.ts import { revalidatePath, revalidateTag } from 'next/cache' import { NextRequest } from 'next/server' export async function POST(req: NextRequest) { const body = await req.json() const secret = req.headers.get('x-revalidate-token') if (secret !== process.env.REVALIDATE_SECRET) { return Response.json({ error: 'Unauthorized' }, { status: 401 }) } // Revalidate specific path after CMS publish event revalidatePath(`/blog/${body.slug}`) revalidateTag('posts') // Revalidate all fetch() calls tagged 'posts' return Response.json({ revalidated: true, slug: body.slug }) }
2.2 Dynamic Rendering — Implementation & When to Use It
Dynamic rendering is a bot-detection + conditional response mechanism: serve pre-rendered HTML to bots, serve SPA to users. It is Google-endorsed as a workaround for CSR-heavy applications but is not a long-term substitute for SSR/SSG. Consider it technical debt.
Dynamic Rendering via Nginx User-Agent Detection
# nginx.conf — Dynamic Rendering proxy configuration # Assumes Rendertron or Puppeteer-Cluster running at localhost:3000 map $http_user_agent $is_bot { default 0; "~*googlebot" 1; "~*bingbot" 1; "~*slurp" 1; "~*duckduckbot" 1; "~*baiduspider" 1; "~*yandexbot" 1; "~*facebot" 1; # Social previews "~*twitterbot" 1; } server { listen 443 ssl http2; server_name codebitbybit.com; location / { if ($is_bot = 1) { rewrite ^ /render/$scheme://$host$request_uri? break; proxy_pass http://localhost:3000; } # Non-bot users: serve SPA normally try_files $uri /index.html; } # Rendertron endpoint proxy location /render/ { proxy_pass http://localhost:3000/render/; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
2.3 DOM vs. View Source — The Rendering Audit Protocol
View Source shows the raw HTTP response. The rendered DOM shows what JavaScript built. For SEO, Google reads the rendered DOM — but any SEO signals that exist only in the DOM (not in View Source) require second-wave rendering, which is expensive and uncertain.
Audit Protocol: Finding DOM-Only SEO Signals
- Open Chrome DevTools → Network → Disable JavaScript → Reload page
- Check: Is canonical tag present? Are meta descriptions present? Is body content visible?
- Compare the same page via curl -A "Googlebot" https://yourpage to see the raw response
- Use Google Search Console → URL Inspection → "View Crawled Page" to see Googlebot's actual rendered DOM
- Any content missing in #2 or #3 but present in #4 must be moved to server-rendered HTML
2.4 Googlebot JavaScript Execution Constraints
Known Googlebot JS Limitations
- Googlebot runs an evergreen Chromium-based renderer but with a headless, resource-constrained execution context
- JavaScript execution timeout: ~5 seconds for synchronous execution, extended for async
- IntersectionObserver-gated lazy loading may cause content below fold to not be rendered
- Fetch/XHR calls made during rendering may or may not complete before snapshot is taken
- Web Workers and SharedArrayBuffers are not reliably executed
- localStorage and sessionStorage are available but ephemeral per-render
Fix: Replace Lazy-Load with Native HTML Loading Attribute
<!-- ❌ WRONG: IntersectionObserver lazy load — Googlebot may miss images --> <img src="placeholder.svg" data-src="real-image.webp" class="lazyload" /> <!-- ✅ CORRECT: Native browser lazy loading — Googlebot understands this --> <img src="real-image.webp" loading="lazy" fetchpriority="low" width="800" height="450" alt="Descriptive alt text for both accessibility and image SEO" /> <!-- ✅ LCP image: NEVER lazy-load, always eager + high fetchpriority --> <img src="hero-image.webp" loading="eager" fetchpriority="high" decoding="async" width="1200" height="600" alt="[Meaningful LCP image description]" />
Advanced Indexation & Canonicalization
Canonical Tags · Pagination · Soft 404s · Index Bloat · Orphan Pages
3.1 Canonical Tag Implementation — Zero-Tolerance Standard
Canonical Signal Hierarchy (Google's Processing Order)
- 301 redirect — strongest signal; Google consolidates all equity to destination
- HTTP Link header — Link: <https://example.com/page>; rel="canonical" — evaluated before HTML parsing
- HTML <link rel="canonical"> — standard and most widely used
- Sitemap inclusion — weakest hint; used as supporting evidence only
Production Canonical Implementation Rules
- Every indexable page MUST have exactly one canonical tag pointing to its own absolute URL
- Use absolute URLs always — relative canonicals are processed but risk misinterpretation
- Canonical URL must use the definitive protocol (HTTPS), subdomain, and trailing-slash convention
- Canonical and Open Graph URL must match — inconsistency confuses social and search parsers
- Canonical must exist in <head> before any JavaScript-injected content
- If page should NOT be indexed: use noindex — do NOT canonical to a different page
Next.js — Programmatic Canonical Generation
// app/blog/[slug]/page.tsx — Absolute canonical via Next.js Metadata API import { Metadata } from 'next' const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://codebitbybit.com' export async function generateMetadata({ params }): Promise<Metadata> { const post = await getPost(params.slug) // Absolute canonical URL — enforces HTTPS + no-trailing-slash convention const canonicalUrl = `${BASE_URL}/blog/${params.slug}` return { title: post.title, description: post.excerpt, alternates: { canonical: canonicalUrl, // Outputs <link rel="canonical" href="..." /> languages: { // Outputs hreflang tags (see Pillar 06) 'en-US': `${BASE_URL}/en/blog/${params.slug}`, 'hi-IN': `${BASE_URL}/hi/blog/${params.slug}`, }, }, openGraph: { url: canonicalUrl, // Must match canonical title: post.title, description: post.excerpt, type: 'article', publishedTime: post.publishedAt.toISOString(), modifiedTime: post.updatedAt.toISOString(), }, } } // ─── CROSS-DOMAIN CANONICAL (Syndicated Content) ─────────────── // If content is syndicated FROM another domain, always canonical to original: alternates: { canonical: 'https://originalsite.com/original-article' // External domain OK }
HTTP Header Canonical for PDFs and Non-HTML Resources
# Nginx: Inject canonical Link header for PDF files # PDFs cannot contain <link> tags — HTTP header is the only option location ~* \.pdf$ { add_header Link '<https://codebitbybit.com/resources/doc.pdf>; rel="canonical"'; add_header X-Robots-Tag "index, follow"; root /var/www/public; }
3.2 Pagination — Current Best Practices Post rel=prev/next
Google officially deprecated rel="prev" and rel="next" in 2019. The canonical signal approach is now the standard. Each paginated page should have a self-referencing canonical and be individually indexed if content is substantive enough.
Pagination Implementation Decision Tree
| Scenario | Strategy | Implementation |
|---|---|---|
| Blog archive (page 2, 3...) | Index all pages; self-canonical each | Unique meta description per page; strong internal linking from page 1 |
| Product listing (>3 pages) | Index page 1; noindex pages 2+ | <meta name="robots" content="noindex, follow"> on page 2+ |
| Infinite scroll | Implement paginated fallback URLs | History API pushState + server-rendered paginated URL responses |
| Load more button | Hybrid: paginated URL segments | Each "load more" loads /products?page=N — crawlable by bot even if hidden from users |
3.3 Soft 404s & Index Bloat Remediation
What Constitutes a Soft 404
- Page returns HTTP 200 but content says "No results found," "Page not found," or similar
- Empty category/tag pages with zero products/posts
- User profile pages for deleted accounts still returning 200
- Search result pages with no query (e.g., /search?q=)
- Thin content pages: <200 words of unique body content
- Auto-generated doorway pages with templated, non-unique content
Programmatic Soft 404 Resolution — Next.js
// app/products/[slug]/page.tsx — Proper 404 handling import { notFound } from 'next/navigation' export default async function ProductPage({ params }) { const product = await getProduct(params.slug) // If product deleted/unpublished → return true 404 (HTTP 404 status) if (!product || product.status === 'deleted') { notFound() // Next.js renders app/not-found.tsx with HTTP 404 } // If product is deprecated but has a replacement → 301 if (product.redirectTo) { redirect(`/products/${product.redirectTo}`, 301) } return <ProductComponent product={product} /> } // app/not-found.tsx — Ensure this returns HTTP 404 (Next.js does this automatically) // Add X-Robots-Tag noindex as defense-in-depth import { headers } from 'next/headers' export default function NotFound() { headers().set('X-Robots-Tag', 'noindex') return ( <div> <h1>404 — Page Not Found</h1> <p>The page you are looking for does not exist.</p> </div> ) }
3.4 Orphan Page Detection & Remediation
Orphan pages are pages that exist in your sitemap or are indexed but have zero internal links pointing to them. Without internal links, they receive no PageRank flow and Googlebot discovers them only through the sitemap — a weak discovery signal.
Orphan Detection Methodology
- Export all URLs from sitemap (URL list A)
- Crawl entire site with Screaming Frog; export all internally-linked URLs (URL list B)
- Orphans = URLs in A that are NOT in B (use Excel VLOOKUP or Python set difference)
- Cross-reference orphan list with GSC impressions — orphans with impressions need immediate internal linking
- Orphans with zero impressions + zero traffic → evaluate for consolidation or 404/410
# Python: Find orphan pages by comparing sitemap URLs vs. crawled internal links import requests, xml.etree.ElementTree as ET from urllib.parse import urlparse def get_sitemap_urls(sitemap_url): r = requests.get(sitemap_url) root = ET.fromstring(r.content) ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'} return {el.text for el in root.findall('.//sm:loc', ns)} # Load internally-crawled URLs from Screaming Frog CSV export import csv def get_crawled_urls(csv_path): with open(csv_path) as f: reader = csv.DictReader(f) return {row['Address'] for row in reader if row['Status Code'] == '200'} sitemap_urls = get_sitemap_urls('https://codebitbybit.com/sitemap.xml') crawled_urls = get_crawled_urls('screaming_frog_export.csv') orphans = sitemap_urls - crawled_urls print(f"Found {len(orphans)} orphan pages:") for url in sorted(orphans): print(url)
Core Web Vitals & Edge Performance
LCP · CLS · INP · Font Loading · Resource Hints · Edge Caching · TTFB
4.1 LCP Technical Remediation
LCP Element Identification & Optimization Path
LCP measures the render time of the largest content element visible in the viewport. Common LCP elements: hero images, background images in CSS, large text blocks, video poster frames.
LCP Waterfall Decomposition
| Sub-metric | What It Measures | Target | Primary Lever |
|---|---|---|---|
| TTFB | Server response time | <600ms | Edge caching, CDN, server optimization |
| Resource Load Delay | Time from TTFB to LCP resource discovery | <200ms | Preload, inline critical CSS, eliminate render-blocking |
| Resource Load Duration | Time to download LCP resource | <200ms | Image compression, WebP/AVIF, CDN edge delivery |
| Element Render Delay | Time from resource loaded to paint | <50ms | Eliminate CSS animations, reduce layout thrash |
LCP Image — Complete Implementation
<!-- In <head>: Preload LCP image immediately — before any CSS or scripts --> <link rel="preload" as="image" href="/hero.webp" imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w" imagesizes="100vw" fetchpriority="high" /> <!-- LCP image element: NEVER lazy, always eager, always explicit dimensions --> <img src="/hero-1200.webp" srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w" sizes="100vw" width="1200" height="600" loading="eager" fetchpriority="high" decoding="async" alt="Hero course image: Prompt Engineering Masterclass" /> <!-- Next.js Image component equivalent (App Router) --> import Image from 'next/image' <Image src="/hero.webp" alt="Prompt Engineering Masterclass hero" width={1200} height={600} priority // Adds preload link + eager loading + high fetchpriority quality={85} sizes="100vw" />
4.2 CLS Technical Remediation
Root Causes & Fixes
| CLS Root Cause | Technical Fix |
|---|---|
| Images without explicit dimensions | Always set width and height attributes on all <img> elements |
| Web fonts causing FOUT/FOIT | font-display: optional for body text; size-adjust for fallback metric matching |
| Dynamically injected banners/ads | Reserve space with fixed-height containers before content loads |
| CSS animations moving layout elements | Use transform and opacity only — these are compositor-only and don't trigger layout |
| Late-loading embeds (Twitter, YouTube) | Use aspect-ratio container; load embed inside fixed dimensions |
| Skeleton screens that collapse | Match skeleton dimensions exactly to loaded content dimensions |
/* CSS: Reserve space for dynamic content to prevent CLS */ /* Video embed with preserved aspect ratio */ .video-container { aspect-ratio: 16 / 9; width: 100%; background: #111; /* Placeholder color while loading */ overflow: hidden; } /* Font-display with size-adjust for minimal FOUT shift */ @font-face { font-family: 'Syne'; src: url('/fonts/syne.woff2') format('woff2'); font-weight: 400 800; font-display: optional; /* Use fallback if font not ready — no swap shift */} /* System font fallback with metrics matching web font */ @font-face { font-family: 'Syne-Fallback'; src: local('Arial'); ascent-override: 90%; descent-override: 20%; line-gap-override: 0%; size-adjust: 105%; } /* Banner ad slot: always reserve space */ .ad-slot { min-height: 90px; /* leaderboard */ width: 100%; background: #f0f0f0; contain: layout; /* CSS containment — prevents paint from affecting parent */ }
4.3 INP Technical Remediation
INP (Interaction to Next Paint) replaced FID in March 2024. It measures the latency of ALL interactions on the page (clicks, keyboard, taps) — not just the first. P75 INP must be <200ms.
INP Decomposition & Optimization Strategy
- Input Delay: Caused by long tasks blocking main thread. Fix: break up tasks with scheduler.yield() or setTimeout(fn, 0)
- Processing Time: Event handler execution time. Fix: move heavy computation to Web Workers, debounce/throttle handlers
- Presentation Delay: Time for browser to repaint after handler. Fix: avoid forced synchronous layouts; batch DOM mutations
// INP Optimization: Break up long tasks with scheduler.yield() // Prevents input delay by yielding to browser between chunks async function processLargeDataset(items) { const CHUNK_SIZE = 50 for (let i = 0; i < items.length; i += CHUNK_SIZE) { const chunk = items.slice(i, i + CHUNK_SIZE) processChunk(chunk) // Yield to browser — allows pending interactions to be processed if ('scheduler' in self && 'yield' in scheduler) { await scheduler.yield() } else { await new Promise(resolve => setTimeout(resolve, 0)) } } } // Debounce search input — prevents INP degradation from rapid keystroke events function debounce(fn, delay = 300) { let timer return (...args) => { clearTimeout(timer) timer = setTimeout(() => fn.apply(this, args), delay) } } const handleSearch = debounce(async (query) => { const results = await fetchSearchResults(query) renderResults(results) }, 300) // Avoid Forced Synchronous Layout — batch reads before writes // ❌ BAD: Read → Write → Read → Write (triggers layout thrash) elements.forEach(el => { const height = el.offsetHeight // READ → forces layout el.style.height = height + 'px' // WRITE → invalidates layout }) // ✅ GOOD: All reads first, then all writes const heights = elements.map(el => el.offsetHeight) // All READs elements.forEach((el, i) => { el.style.height = heights[i] + 'px' }) // All WRITEs
4.4 Font Preloading & Resource Hints — Precision Configuration
<!-- <head> resource hints — ORDER IS CRITICAL --> <!-- 1. dns-prefetch: Resolve DNS for third-party domains early --> <link rel="dns-prefetch" href="//fonts.googleapis.com" /> <link rel="dns-prefetch" href="//cdn.codebitbybit.com" /> <!-- 2. preconnect: Establish TCP+TLS for high-priority third-parties --> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://api.codebitbybit.com" /> <!-- 3. preload: Fetch LCP image and critical font IMMEDIATELY --> <link rel="preload" href="/fonts/syne-variable.woff2" as="font" type="font/woff2" crossorigin="anonymous" <!-- Required for CORS font resources --> /> <link rel="preload" href="/hero.webp" as="image" fetchpriority="high" /> <!-- 4. prefetch: Load resources for NEXT page navigation --> <link rel="prefetch" href="/courses" as="document" /> <!-- 5. Critical CSS: Inline above-the-fold styles — eliminate render-blocking --> <style> /* Inline ONLY critical CSS (first-viewport styles) */ /* Generate with: npx critical index.html --inline */ body { margin: 0; font-family: 'Syne', sans-serif; } .hero { min-height: 60vh; background: #080C14; } </style> <!-- 6. Non-critical CSS: Load asynchronously --> <link rel="stylesheet" href="/styles/main.css" media="print" onload="this.media='all'" /> <noscript><link rel="stylesheet" href="/styles/main.css"/></noscript>
4.5 Edge Caching & TTFB Optimization
Cloudflare Cache Configuration via Page Rules
# Cloudflare: _headers file (Cloudflare Pages / Workers Sites) # Or configure via Dashboard → Caching → Cache Rules # ─── Static Assets: Maximum cache at edge ────────────────── /fonts/* Cache-Control: public, max-age=31536000, immutable Vary: Accept-Encoding /_next/static/* Cache-Control: public, max-age=31536000, immutable /images/* Cache-Control: public, max-age=2592000, stale-while-revalidate=86400 Vary: Accept # For WebP negotiation # ─── HTML Pages: Edge cache with revalidation ────────────── /blog/* Cache-Control: public, s-maxage=3600, stale-while-revalidate=300 # s-maxage: CDN cache for 1 hour; stale-while-revalidate: serve stale for 5 min while fetching fresh # ─── API Routes: No edge caching ─────────────────────────── /api/* Cache-Control: private, no-store # ─── Sitemap: Cache at edge with periodic purge ──────────── /sitemap*.xml Cache-Control: public, s-maxage=3600, stale-while-revalidate=600 Content-Type: application/xml; charset=utf-8
Streaming SSR with React Suspense — SEO Safe Pattern
// app/courses/[slug]/page.tsx — Streaming SSR: critical content first import { Suspense } from 'react' export default function CoursePage({ params }) { return ( <> {/* Renders immediately — not in Suspense — Googlebot sees this in first chunk */} <CourseHero slug={params.slug} /> <CourseDescription slug={params.slug} /> {/* Deferred: reviews can load after initial paint */} <Suspense fallback={<ReviewsSkeleton />}> <CourseReviews slug={params.slug} /> </Suspense> {/* Deferred: recommendations don't affect SEO critical path */} <Suspense fallback={<RecommendationsSkeleton />}> <RelatedCourses slug={params.slug} /> </Suspense> </> ) }
The Semantic Web & Structured Data
JSON-LD · Schema.org · Nested Schemas · Entity Graphs · Rich Results
5.1 Advanced Schema.org Mapping
Schema Implementation Rules
- Use JSON-LD exclusively — Google recommends it; Microdata and RDFa are legacy
- JSON-LD should be injected server-side (in <head> or <body>) — never client-side only
- All JSON-LD properties must be resolvable: URLs must return 200; images must be crawlable
- Never mark up content that isn't visible on the page — this is a spam policy violation
- Entity deduplication: Use @id consistently across schemas to connect entity graph
- Validate with Google Rich Results Test + Schema Markup Validator before deploy
Complete Course Schema — Nested Entity Graph
<!-- JSON-LD: Course page with nested Organization, Person, AggregateRating --> <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "Course", "@id": "https://codebitbybit.com/courses/prompt-engineering-masterclass#course", "name": "Prompt Engineering Masterclass — Master the Art of Prompt Engineering", "description": "Comprehensive hands-on course covering real-world prompt engineering techniques for AI tools including ChatGPT, Claude, Gemini, and Midjourney.", "url": "https://codebitbybit.com/courses/prompt-engineering-masterclass", "image": { "@type": "ImageObject", "url": "https://cdn.codebitbybit.com/courses/prompt-engineering-og.webp", "width": 1200, "height": 630 }, "provider": { "@id": "https://codebitbybit.com/#organization" }, "instructor": { "@id": "https://codebitbybit.com/instructor/chandra#person" }, "offers": { "@type": "Offer", "price": "19.99", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "validFrom": "2025-01-01" }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.8", "reviewCount": "342", "bestRating": "5", "worstRating": "1" }, "hasCourseInstance": { "@type": "CourseInstance", "courseMode": "Online", "courseWorkload": "PT8H", "inLanguage": "en" }, "teaches": [ "Prompt Engineering", "AI Tools", "ChatGPT", "Claude AI" ], "educationalLevel": "Beginner" }, { // Organization entity — reused via @id across all pages "@type": "Organization", "@id": "https://codebitbybit.com/#organization", "name": "CodeBitByBit", "url": "https://codebitbybit.com", "logo": { "@type": "ImageObject", "url": "https://codebitbybit.com/logo.png", "width": 512, "height": 512 }, "sameAs": [ "https://twitter.com/codebitbybit", "https://linkedin.com/company/codebitbybit", "https://youtube.com/@codebitbybit" ] }, { "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://codebitbybit.com" }, { "@type": "ListItem", "position": 2, "name": "Courses", "item": "https://codebitbybit.com/courses" }, { "@type": "ListItem", "position": 3, "name": "Prompt Engineering Masterclass" } ] } ] } </script>
5.2 Programmatic JSON-LD Injection
JSON-LD Factory Pattern — Next.js
// lib/schema-factory.ts — Centralized schema generation // Ensures entity @id consistency across all pages const BASE = 'https://codebitbybit.com' export function generateArticleSchema(article: Article) { return { "@context": "https://schema.org", "@graph": [ { "@type": "Article", "@id": `${BASE}/blog/${article.slug}#article`, "headline": article.title, "description": article.excerpt, "datePublished": article.publishedAt.toISOString(), "dateModified": article.updatedAt.toISOString(), "author": { "@id": `${BASE}/authors/${article.authorSlug}#person` }, "publisher": { "@id": `${BASE}/#organization` }, "image": { "@type": "ImageObject", "url": article.ogImage, "width": 1200, "height": 630 }, "mainEntityOfPage": { "@id": `${BASE}/blog/${article.slug}` }, }, generateBreadcrumbs([ { name: 'Home', url: BASE }, { name: 'Blog', url: `${BASE}/blog` }, { name: article.title, url: `${BASE}/blog/${article.slug}` }, ]), ] } } export function generateBreadcrumbs(items: {name: string, url?: string}[]) { return { "@type": "BreadcrumbList", "itemListElement": items.map((item, index) => ({ "@type": "ListItem", "position": index + 1, "name": item.name, ...(item.url && { "item": item.url }), })) } } // Usage in page.tsx: import Script from 'next/script' import { generateArticleSchema } from '@/lib/schema-factory' export default function BlogPost({ article }) { const schema = generateArticleSchema(article) return ( <> <Script id="article-schema" type="application/ld+json" strategy="beforeInteractive" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /> {/* Page content */} ) }
5.3 Schema Type Priority Matrix for codebitbybit.com
| Page Type | Primary Schema | Supporting Schemas | Rich Result Potential |
|---|---|---|---|
| Course page | Course + CourseInstance | BreadcrumbList, Organization, AggregateRating | Course carousel |
| Blog article | Article (or BlogPosting) | Person, Organization, BreadcrumbList | Top Stories |
| Homepage | WebSite + Organization | SearchAction (Sitelinks Searchbox) | Sitelinks |
| FAQ page | FAQPage | BreadcrumbList | FAQ accordion |
| How-to guide | HowTo | BreadcrumbList, Article | How-to steps |
| Video tutorial | VideoObject | BreadcrumbList, Course | Video rich result |
| Author profile | Person | Organization (memberOf) | Entity card |
Internationalization & Localization
hreflang · x-default · Regional Canonicals · Subdomain vs. Subdirectory
6.1 hreflang — Absolute Technical Specification
hreflang Implementation Methods Compared
| Method | Best For | Technical Requirement | Googlebot Support |
|---|---|---|---|
| HTML <link> in <head> | SSR/SSG sites; primary recommendation | Full page control; must be server-rendered | Full |
| HTTP Link header | PDFs, non-HTML resources | Web server configuration required | Full |
| XML Sitemap | Large sites; reduces in-page bloat | Sitemap must be submitted to all GSC properties | Full |
hreflang HTML Implementation — Zero-Error Standard
<!-- EVERY alternate must be listed on EVERY page in the set. If page A lists B and C, then B must list A and C, and C must list A and B. Unreciprocated hreflang is IGNORED by Google. --> <head> <!-- Self-referencing canonical (always) --> <link rel="canonical" href="https://codebitbybit.com/en/courses/prompt-engineering" /> <!-- hreflang annotations: language-region pairs --> <link rel="alternate" hreflang="en" href="https://codebitbybit.com/en/courses/prompt-engineering" /> <link rel="alternate" hreflang="en-US" href="https://codebitbybit.com/en-us/courses/prompt-engineering" /> <link rel="alternate" hreflang="en-GB" href="https://codebitbybit.com/en-gb/courses/prompt-engineering" /> <link rel="alternate" hreflang="hi" href="https://codebitbybit.com/hi/courses/prompt-engineering" /> <link rel="alternate" hreflang="es" href="https://codebitbybit.com/es/courses/prompt-engineering" /> <link rel="alternate" hreflang="x-default" href="https://codebitbybit.com/courses/prompt-engineering" /> </head>
hreflang Sitemap Implementation — For Large Localized Sites
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"> <url> <!-- Each <url> block lists ALL locale variants for one piece of content --> <loc>https://codebitbybit.com/en/courses/prompt-engineering</loc> <xhtml:link rel="alternate" hreflang="en" href="https://codebitbybit.com/en/courses/prompt-engineering"/> <xhtml:link rel="alternate" hreflang="hi" href="https://codebitbybit.com/hi/courses/prompt-engineering"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://codebitbybit.com/courses/prompt-engineering"/> </url> <url> <loc>https://codebitbybit.com/hi/courses/prompt-engineering</loc> <xhtml:link rel="alternate" hreflang="en" href="https://codebitbybit.com/en/courses/prompt-engineering"/> <xhtml:link rel="alternate" hreflang="hi" href="https://codebitbybit.com/hi/courses/prompt-engineering"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://codebitbybit.com/courses/prompt-engineering"/> </url> </urlset>
6.2 x-default Handling — Precision Rules
When to Use x-default
- x-default signals to Google the "catch-all" URL for users whose locale doesn't match any other hreflang annotation
- Always point x-default to your language-selector page or your primary English version — never to a 404 or redirect
- If the same URL serves as the English version AND the default, you can use it for both: hreflang="en" AND hreflang="x-default" pointing to the same URL
- Never have x-default point to a region-specific URL (e.g., /en-US/) — use the non-regionalized version
6.3 Preventing Regional Canonical Conflicts
The Canonical + hreflang Conflict Rule
URL Structure: Subdirectory vs. Subdomain vs. ccTLD
| Structure | Example | SEO Pros | SEO Cons |
|---|---|---|---|
| Subdirectory | /en/, /hi/, /es/ | Shared domain authority; easiest to implement; single GSC property | URL structure more complex |
| Subdomain | en.site.com, hi.site.com | Cleaner separation; easy server routing | Google treats subdomains semi-independently; splits link equity |
| ccTLD | site.in, site.co.uk | Strongest geo-targeting signal | Requires separate GSC properties; equity not shared; high overhead |
Architecture, Security & Accessibility
Information Architecture · SSL/HSTS · WCAG/ARIA · Semantic HTML5
7.1 Information Architecture & Flat Site Structure
The PageRank Depth Principle
Internal PageRank flows inversely with click depth. Pages more than 3 clicks from the homepage receive significantly less internal PageRank flow and are crawled less frequently. The flat architecture principle: no important page should be more than 3 clicks from the homepage.
Crawl Depth Architecture for codebitbybit.com
Level 0 (Homepage): /
Level 1 (Hub pages): /courses/, /blog/, /tutorials/
Level 2 (Category): /courses/ai-tools/, /blog/prompt-engineering/
Level 3 (Content): /courses/ai-tools/prompt-engineering-masterclass
/blog/prompt-engineering/chain-of-thought-guide
Level 4 (Avoid): /blog/tag/ai/page/4 ← These should have noindex or be blocked
URL Depth Budget Rule:
- Priority content → Levels 1-3 only
- Paginated archives → noindex beyond page 2
- Tag/category combinations → canonical to primary or blocked
Internal Linking: PageRank Distribution Strategy
- Hub-and-spoke model: Create topic cluster pages (/prompt-engineering-guide/) that link to all related articles, and all articles link back to the hub
- Contextual internal links: Use keyword-rich anchor text within body content (not just navigation) — these carry stronger PageRank signals
- Link from high-authority pages: Identify pages with the most external backlinks; ensure they link internally to priority pages
- Fix broken internal links monthly: Every broken internal link is wasted PageRank flow; audit with Screaming Frog weekly
- Avoid nofollow on internal links: Internal nofollow blocks PageRank flow — remove from internal navigation
Nginx: Enforcing Canonical URL Structure via Redirects
# nginx.conf — Canonical URL enforcement # All variants must 301 to the single canonical form server { # 1. Redirect HTTP → HTTPS listen 80; server_name codebitbybit.com www.codebitbybit.com; return 301 https://codebitbybit.com$request_uri; } server { listen 443 ssl http2; server_name www.codebitbybit.com; # 2. Redirect www → non-www (canonical domain) return 301 https://codebitbybit.com$request_uri; } server { listen 443 ssl http2; server_name codebitbybit.com; # 3. Enforce no trailing slash (or consistently WITH — pick one, never mix) rewrite ^(/.+)/$ $1 permanent; # Remove trailing slash # 4. Force lowercase URLs if ($request_uri ~* [A-Z]") { rewrite ^ $scheme://$host${request_uri,,} permanent; } }
7.2 SSL/TLS Configuration & HSTS — SEO Impact
Why SSL Configuration Is an SEO Concern
- HTTPS is a confirmed Google ranking signal (since 2014)
- Mixed content (HTTPS page loading HTTP resources) triggers browser warnings — increases bounce rate
- HTTP/2 and HTTP/3 are only available over TLS — both significantly improve CWV by enabling multiplexing and reduced connection overhead
- Incorrect SSL certificate configuration (expired cert, wrong domain, self-signed) causes browser hard blocks — zero crawl possible
Complete Nginx SSL + HSTS + Security Headers Configuration
server { listen 443 ssl http2; server_name codebitbybit.com; # ─── TLS Configuration ───────────────────────────────────── ssl_certificate /etc/letsencrypt/live/codebitbybit.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/codebitbybit.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; # Disable TLS 1.0, 1.1 ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384'; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; ssl_stapling on; # OCSP stapling — reduces handshake latency ssl_stapling_verify on; # ─── HSTS — Must be set carefully before enabling preload ── add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # WARNING: Submit to HSTS preload list only after confirming ALL subdomains support HTTPS # Preload list: https://hstspreload.org # ─── Security Headers (also contribute to page quality signals) ─ add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # ─── Content Security Policy ─────────────────────────────── add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.codebitbybit.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https: blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.codebitbybit.com;" always; }
7.3 WCAG Accessibility Standards & Their Direct Impact on Bot Crawling
The Accessibility-SEO Overlap Model
Googlebot is functionally an accessibility tool. It reads content like a screen reader — it depends on semantic HTML, ARIA labels, and proper document structure to understand page hierarchy and content relationships. Accessibility improvements are SEO improvements.
| WCAG Requirement | SEO Impact | Implementation |
|---|---|---|
| Descriptive alt text on images | Image indexation; visual search; context signals | Describe the image content; include keywords naturally; never keyword-stuff |
| Semantic heading hierarchy (H1→H6) | Content structure signals; featured snippets eligibility | One H1 per page; logical H2→H3 nesting; never skip levels |
| Descriptive link text | Anchor text signals to linked page | Replace "click here" with descriptive text matching destination content |
| ARIA landmark roles | Helps Googlebot identify main content vs. navigation vs. footer | Use main, nav, header, footer, aside semantic elements |
| Language attribute | Language detection accuracy; affects locale targeting | <html lang="en"> — always set; use locale variant for i18n pages |
| Keyboard navigability | Ensures bot can follow all interactive elements | All interactive elements reachable via tab order; no JS-only interactions for links |
Semantic HTML5 Template — SEO + Accessibility Optimized
<!DOCTYPE html> <html lang="en"> <head> <!-- [All head meta, canonical, hreflang, preloads here] --> </head> <body> <!-- Skip link: accessibility + allows bots to identify main content --> <a href="#main-content" class="sr-only focus:not-sr-only">Skip to main content</a> <header role="banner"> <nav aria-label="Primary navigation"> <ul role="list"> <li><a href="/courses">Courses</a></li> <li><a href="/blog">Blog</a></li> </ul> </nav> </header> <main id="main-content" role="main"> <!-- Breadcrumb nav: both accessibility + supports Schema BreadcrumbList --> <nav aria-label="Breadcrumb"> <ol itemscope itemtype="https://schema.org/BreadcrumbList"> <li itemprop="itemListElement"> <a href="/" itemprop="item">Home</a> </li> </ol> </nav> <article aria-labelledby="article-title"> <!-- One H1 per page; mirrors <title> tag but can be longer --> <h1 id="article-title">Prompt Engineering Masterclass</h1> <section aria-labelledby="section-intro"> <h2 id="section-intro">What You Will Learn</h2> <!-- Section content --> </section> </article> <aside aria-label="Related courses"> <!-- Sidebar content: labeled as 'aside' so Googlebot deprioritizes it relative to <main> content --> </aside> </main> <footer role="contentinfo"> <nav aria-label="Footer navigation"> <!-- Footer links --> </nav> </footer> </body> </html>
Enterprise Technical SEO Tool Stack
| Category | Tool | Primary Use | Tier |
|---|---|---|---|
| Crawling & Audit | Screaming Frog SEO Spider | Full site crawl, redirect chains, soft 404 detection, hreflang audit | Essential |
| Log Analysis | Screaming Frog Log Analyzer / GoAccess | Googlebot crawl frequency, budget analysis, bot behavior | Essential |
| Search Console | Google Search Console | Indexation status, coverage errors, CWV report, URL inspection | Essential |
| Performance | PageSpeed Insights / CrUX | CWV field data (real users), lab data, recommendations | Essential |
| Schema Testing | Rich Results Test / Schema Validator | JSON-LD validation, rich result eligibility | Essential |
| Rendering Audit | Chrome DevTools (JS disabled) + GSC URL Inspection | DOM vs. source comparison, Googlebot render simulation | Essential |
| Enterprise Crawl | Botify / Lumar (DeepCrawl) | 1M+ URL crawl analysis, crawl budget reporting, log integration | Enterprise |
| Rank Tracking | SEMrush / Ahrefs | SERP position tracking, visibility trends, competitor analysis | Core |
| Hreflang Audit | hreflang.ninja / Ahrefs Site Audit | Detect unreciprocated hreflang, wrong language codes | Core |
| Monitoring | Cloudflare Analytics + GSC API | Real-time crawl monitoring, automated alerts for coverage drops | Ops |
Technical SEO Audit Checklist — Pre-Launch & Quarterly
Crawl & Indexation
- robots.txt exists, is accessible, and is syntactically valid
- All critical pages are NOT blocked in robots.txt
- XML sitemap index is live and all child sitemaps return 200
- All sitemap URLs return 200 (no 301s, 404s in sitemap)
- Sitemap submitted to Google Search Console and Bing Webmaster Tools
- No redirect chains longer than 1 hop
- All pages return correct HTTP status codes (no soft 404s)
- URL parameter handling configured (robots.txt + canonicals)
- Log file analysis run — no excessive bot 404s or 5xx errors
- Crawl budget not wasted on session IDs, tracking parameters
Canonicalization
- Every indexable page has exactly one self-referencing canonical
- Canonical uses absolute URL with correct protocol and domain
- Canonical and OG URL are identical on all pages
- No canonical tag conflicts across www/non-www, HTTP/HTTPS
- Paginated pages have appropriate canonical or noindex
- Syndicated content has canonical pointing to original source
Core Web Vitals
- LCP < 2.5s on mobile (CrUX P75 field data)
- CLS < 0.1 on all page templates
- INP < 200ms on mobile
- TTFB < 600ms at P75
- LCP image has preload link in <head>
- All images have explicit width + height attributes
- No render-blocking resources above the fold
- Critical CSS is inlined; non-critical CSS is async loaded
- Web fonts use font-display: optional or swap
Rendering & JavaScript
- Critical SEO elements (title, canonical, meta, hreflang) present in View Source
- GSC URL Inspection shows rendered DOM matches View Source for key content
- No IntersectionObserver lazy-loading for content in LCP area
- LCP image uses loading="eager" and fetchpriority="high"
- High-value pages use SSR or SSG (not CSR)
- ISR revalidation configured for frequently-updated content
Structured Data
- All JSON-LD validated via Rich Results Test
- No structured data markup without corresponding visible content
- @id values are consistent and absolute across all schemas
- BreadcrumbList present on all non-homepage pages
- Organization schema with sameAs present on homepage
- Course, Article, or relevant type schema on all content pages
i18n & Security
- All hreflang annotations are reciprocated (bidirectional)
- x-default points to correct catch-all URL
- Each locale has self-referencing canonical (not cross-locale canonical)
- SSL certificate valid; TLS 1.0/1.1 disabled
- HSTS header present with appropriate max-age
- OCSP stapling enabled
- All pages use HTML lang attribute
- One H1 per page; logical heading hierarchy
- All images have descriptive alt attributes
- No JS-only navigation links (all critical links in HTML anchor tags)
This document must be reviewed quarterly and updated upon any significant Google algorithm update or Core Web Vitals threshold revision.
