SEO
Advanced
SEO for JavaScript Frameworks
251 views
75 min read
Table of Contents
Introduction to JavaScript SEO
Modern JavaScript frameworks like React, Vue, and Angular create unique SEO challenges. Understanding how search engines process JavaScript is essential for modern web development.
JavaScript Rendering Challenges
- Search engines must execute JavaScript to see content
- Rendering delays can impact crawl efficiency
- Client-side routing complicates indexing
- Dynamic content may not be discovered
1. How Google Renders JavaScript
Understanding Google rendering pipeline is fundamental to JavaScript SEO.
Google's Two-Wave Indexing
# Google JavaScript Processing Pipeline
Wave 1: Initial Crawl
├── Fetch HTML
├── Parse HTML content
├── Extract links from HTML
├── Index HTML content
└── Queue for rendering
Rendering Queue (Hours to Days)
├── Chromium-based renderer
├── Execute JavaScript
├── Wait for network requests
└── Capture rendered DOM
Wave 2: Post-Render Indexing
├── Process rendered content
├── Extract dynamic links
├── Update index with full content
└── Re-evaluate rankings
# Key Insight: Critical content should be
# available without JavaScript when possible
Testing JavaScript Rendering
# Testing Tools
1. Google Search Console URL Inspection
- Shows rendered HTML
- Identifies rendering issues
- Displays screenshot
2. Mobile-Friendly Test
- Quick rendering check
- Shows rendered HTML
- Flags JavaScript errors
3. Chrome DevTools
- Disable JavaScript: Settings > Debugger > Disable JavaScript
- View what Googlebot sees without JS
4. View Source vs. Inspect Element
- View Source = Initial HTML (Wave 1)
- Inspect Element = Rendered DOM (Wave 2)
2. Server-Side Rendering (SSR)
SSR delivers fully rendered HTML to search engines and users.
Next.js SSR Implementation
// pages/product/[slug].js - Next.js SSR
export async function getServerSideProps(context) {
const { slug } = context.params;
// Fetch data on server
const product = await fetchProduct(slug);
if (!product) {
return {
notFound: true // Returns 404
};
}
return {
props: {
product,
// SEO data available immediately
seoData: {
title: product.name,
description: product.description,
image: product.image
}
}
};
}
export default function ProductPage({ product, seoData }) {
return (
<>
<Head>
<title>{seoData.title}</title>
<meta name="description"
content={seoData.description} />
<meta property="og:image"
content={seoData.image} />
</Head>
<ProductDisplay product={product} />
</>
);
}
Nuxt.js SSR Implementation
// pages/product/_slug.vue - Nuxt.js SSR
<template>
<div>
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>
</div>
</template>
<script>
export default {
async asyncData({ params, $axios }) {
const product = await $axios.$get(
`/api/products/${params.slug}`
);
return { product };
},
head() {
return {
title: this.product.name,
meta: [
{
hid: "description",
name: "description",
content: this.product.description
},
{
hid: "og:title",
property: "og:title",
content: this.product.name
}
]
};
}
};
</script>
3. Static Site Generation (SSG)
Pre-rendering pages at build time for optimal performance and SEO.
Next.js Static Generation
// pages/blog/[slug].js - Next.js SSG
// Generate paths at build time
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map(post => ({
params: { slug: post.slug }
})),
fallback: "blocking" // SSR for new paths
};
}
// Generate page content at build time
export async function getStaticProps({ params }) {
const post = await getPostBySlug(params.slug);
return {
props: { post },
revalidate: 3600 // ISR: regenerate hourly
};
}
export default function BlogPost({ post }) {
return (
<article>
<Head>
<title>{post.title}</title>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(post.schema)
}}
/>
</Head>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{
__html: post.content
}} />
</article>
);
}
4. Dynamic Rendering
Serving different content to users vs. search engine bots.
Dynamic Rendering Setup
// middleware/dynamic-render.js
const BOTS = [
"googlebot",
"bingbot",
"yandex",
"baiduspider",
"facebookexternalhit",
"twitterbot",
"linkedinbot"
];
function isBot(userAgent) {
const ua = userAgent.toLowerCase();
return BOTS.some(bot => ua.includes(bot));
}
module.exports = async (req, res, next) => {
if (isBot(req.headers["user-agent"] || "")) {
// Render with Puppeteer/Rendertron
const renderedHtml = await prerenderPage(req.url);
return res.send(renderedHtml);
}
// Serve SPA to users
next();
};
// Rendertron service configuration
async function prerenderPage(url) {
const rendertronUrl = process.env.RENDERTRON_URL;
const response = await fetch(
`${rendertronUrl}/render/${encodeURIComponent(url)}`
);
return response.text();
}
Rendertron Docker Setup
# docker-compose.yml
version: "3"
services:
rendertron:
image: "ammobindotca/rendertron"
ports:
- "3000:3000"
environment:
- CACHE_MAX_ENTRIES=100
- CACHE_EXPIRY_SECONDS=3600
restart: unless-stopped
# nginx.conf - Bot detection and routing
map $http_user_agent $is_bot {
default 0;
~*(googlebot|bingbot|yandex) 1;
}
server {
location / {
if ($is_bot) {
proxy_pass http://rendertron:3000/render/$scheme://$host$request_uri;
}
try_files $uri $uri/ /index.html;
}
}
5. Client-Side SEO Optimization
When SSR is not possible, optimize client-side rendering for SEO.
React Helmet for Meta Tags
// components/SEO.jsx - React Helmet
import { Helmet } from "react-helmet-async";
export default function SEO({
title,
description,
image,
url,
type = "website"
}) {
const siteTitle = "Your Site Name";
const fullTitle = `${title} | ${siteTitle}`;
return (
<Helmet>
<title>{fullTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
{/* Open Graph */}
<meta property="og:type" content={type} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:url" content={url} />
{/* Twitter */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
</Helmet>
);
}
SPA Routing Best Practices
// Proper client-side routing for SEO
// 1. Use history mode (not hash mode)
// React Router
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products/:slug" element={<Product />} />
</Routes>
</BrowserRouter>
// Vue Router
const router = createRouter({
history: createWebHistory(), // NOT createWebHashHistory()
routes: [...]
});
// 2. Server configuration for SPA
// All routes should return index.html
# nginx.conf
location / {
try_files $uri $uri/ /index.html;
}
# Apache .htaccess
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
Key Terms
- Server-Side Rendering (SSR)
- Rendering JavaScript on the server and sending fully formed HTML to the client
- Static Site Generation (SSG)
- Pre-rendering pages at build time into static HTML files
- Incremental Static Regeneration (ISR)
- Updating static pages after deployment without rebuilding the entire site
- Dynamic Rendering
- Serving pre-rendered content to bots while serving the SPA to users
Practical Exercise
- Test your JavaScript site using Google's URL Inspection tool
- Implement SSR for your most important pages
- Set up ISR for content that changes periodically
- Configure proper meta tag management for your framework
- Create a sitemap that includes all JavaScript-rendered routes