Technical SEO 15 min read

JavaScript SEO Guide: How to Make JS Sites Search-Friendly

Complete guide to JavaScript SEO. Learn how search engines render JavaScript, common issues, and how to ensure your JS-heavy site gets indexed properly.

By Rankture Team
JavaScript SEO Guide: How to Make JS Sites Search-Friendly

JavaScript powers modern web applications, but it creates unique challenges for SEO. Search engines must render JavaScript to see your content—and they don’t always do it perfectly.

This guide covers everything you need to know about JavaScript SEO.

How Search Engines Process JavaScript

Google processes JavaScript pages in two waves:

Wave 1: Initial HTML Parsing

  1. Googlebot crawls the URL
  2. Parses the initial HTML response
  3. Indexes basic content (if any exists in HTML)
  4. Discovers links in HTML
  5. Adds page to render queue

Wave 2: Rendering

  1. Page enters render queue
  2. Googlebot renders JavaScript using Chrome
  3. Indexes additional content discovered
  4. Discovers links added by JavaScript

The gap between waves can be days or weeks. This delay is why JavaScript SEO is challenging.

Common JavaScript SEO Problems

1. Content Not in Initial HTML

Problem: Content only appears after JavaScript executes.

<!-- What Google sees initially -->
<div id="app"></div>

<!-- What Google needs to see -->
<div id="app">
  <h1>Your Page Title</h1>
  <p>Your actual content...</p>
</div>

Impact: Delayed indexing, possibly incomplete indexing.

Problem: JavaScript-powered links that Google can’t follow.

// NOT CRAWLABLE
<div onclick="goToPage('/product')">View Product</div>

// NOT CRAWLABLE - no href
<a onclick="navigate('/product')">View Product</a>

// CRAWLABLE - proper anchor tag
<a href="/product">View Product</a>

Impact: Internal pages won’t be discovered or indexed.

3. Blocked JavaScript Resources

Problem: CSS or JS files blocked by robots.txt.

# DON'T DO THIS
User-agent: *
Disallow: /js/
Disallow: /css/

Impact: Google can’t render your page properly.

4. JavaScript Errors

Problem: JavaScript errors prevent content from rendering.

Impact: Content never appears for Google to index.

5. Lazy Loading Issues

Problem: Content loaded on scroll/interaction never renders.

// Google won't see this content
document.addEventListener('scroll', loadMoreContent);

// Or click-to-load
button.addEventListener('click', loadContent);

Impact: Below-fold content not indexed.

6. Client-Side Routing Issues

Problem: SPAs with client-side routing that don’t work without JavaScript.

// URL changes but server returns same HTML
history.pushState({}, '', '/new-page');

Impact: Direct URL access fails, poor crawlability.

Testing JavaScript SEO

Method 1: Google Search Console URL Inspection

  1. Enter URL in Search Console
  2. Click “Test Live URL”
  3. View “Tested page” > “View tested page”
  4. Compare HTML to rendered HTML
site:yourdomain.com "specific content text"

If Google can’t find content that exists on your page, it’s not being indexed.

Method 3: Mobile-Friendly Test

  1. Enter URL in Google’s Mobile-Friendly Test
  2. View rendered page screenshot
  3. Check rendered HTML for your content

Method 4: Disable JavaScript

  1. Disable JavaScript in browser DevTools
  2. Visit your pages
  3. Check if content/links are visible

If nothing appears, neither will Google see anything in the first pass.

Solutions for JavaScript SEO

Option 1: Server-Side Rendering (SSR)

How it works: Server renders JavaScript and sends complete HTML.

Best for: React (Next.js), Vue (Nuxt), Angular (Angular Universal)

Pros:

Cons:

Example (Next.js):

// pages/product/[id].js
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id);
  return { props: { product } };
}

export default function ProductPage({ product }) {
  return <ProductDetails product={product} />;
}

Option 2: Static Site Generation (SSG)

How it works: Pages pre-rendered at build time.

Best for: Content that doesn’t change frequently.

Pros:

Cons:

Example (Next.js):

export async function getStaticProps({ params }) {
  const product = await fetchProduct(params.id);
  return { props: { product } };
}

export async function getStaticPaths() {
  const products = await fetchAllProducts();
  return {
    paths: products.map(p => ({ params: { id: p.id } })),
    fallback: 'blocking',
  };
}

Option 3: Hybrid Rendering

How it works: Combine SSR, SSG, and client-side based on page needs.

Best for: Complex sites with varying needs.

Example:

Option 4: Dynamic Rendering

How it works: Serve pre-rendered HTML to bots, JavaScript to users.

Best for: Sites that can’t implement SSR.

Tools: Prerender.io, Puppeteer, Rendertron

How it works:

  1. Detect if request is from a bot (user-agent)
  2. If bot: serve pre-rendered static HTML
  3. If user: serve normal JavaScript app

Important: Google allows this, but it must render the same content for both.

Option 5: Progressive Enhancement

How it works: Build with HTML/CSS first, enhance with JavaScript.

Best for: Content-focused sites.

Approach:

  1. Core content works without JavaScript
  2. JavaScript adds interactivity
  3. Everything degrades gracefully

JavaScript SEO Best Practices

<!-- CORRECT -->
<a href="/page">Link Text</a>

<!-- WRONG -->
<span onclick="goToPage('/page')">Link Text</span>
<a onclick="navigate('/page')">Link Text</a>
<button onclick="location='/page'">Link Text</button>

2. Implement History API Correctly

For SPAs with client-side routing:

// Push state changes URL
history.pushState({ page: 'about' }, '', '/about');

// But server must also handle /about directly!

Ensure your server returns appropriate HTML for any URL.

3. Handle Meta Tags Dynamically

Update title and meta descriptions for each page:

// React Helmet example
import { Helmet } from 'react-helmet';

function ProductPage({ product }) {
  return (
    <>
      <Helmet>
        <title>{product.name} | Your Site</title>
        <meta name="description" content={product.description} />
      </Helmet>
      <ProductDetails product={product} />
    </>
  );
}

4. Use Canonical Tags

Set canonical URLs for JavaScript-generated pages:

<Helmet>
  <link rel="canonical" href={`https://example.com${router.pathname}`} />
</Helmet>

5. Avoid Lazy Loading Critical Content

Above-fold content should load immediately:

// Critical content - load immediately
<div>{mainContent}</div>

// Below-fold content - can lazy load
<LazyComponent onVisible={() => loadMoreContent()} />

6. Ensure Resources Aren’t Blocked

Check robots.txt allows JS/CSS:

User-agent: *
Allow: /js/
Allow: /css/
Allow: /_next/  # Next.js
Allow: /static/

7. Handle Errors Gracefully

Don’t let JavaScript errors break the page:

// Error boundary (React)
class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

8. Implement Structured Data

Add JSON-LD in a way that’s present in rendered HTML:

// Add to head or body
<script type="application/ld+json">
  {JSON.stringify({
    "@context": "https://schema.org",
    "@type": "Product",
    "name": product.name,
    "description": product.description,
  })}
</script>

JavaScript Framework Specific Tips

React

Vue

Angular

Vanilla JavaScript

JavaScript SEO Checklist


Related Resources:

Tags:

javascript seo technical seo rendering indexation spa react vue

Share this article:

Ready to improve your SEO?

Get a free SEO audit and see exactly what needs fixing on your site

Start Free Audit