JavaScript SEO Example

Written by Backbone Tutorials Team

Last updated: June 2026 · 9 min read

A client-rendered page can rank, but only if each route exposes correct signals to crawlers: a unique title, a matching meta description, a self-referencing canonical, and structured data that reflects the visible content. This example shows a small single page application updating all of those on every navigation, the practical core of making JavaScript content crawlable.

For the full picture of how search engines process JavaScript, see JavaScript SEO and how Google crawls JavaScript.

How the JavaScript SEO example stays crawlableOn each route the app updates the title, meta description, canonical, and structured data so crawlers see correct per-page signals. route change title + meta canonical JSON-LD updated crawlable

What this example builds

The example wires per-route metadata into a single page application so that each view sets its own title, description, canonical URL, and JSON-LD before the content shows.

The behaviour you will see

Navigating between routes changes the document title and meta description, repoints the canonical link to the current URL, and swaps the structured data block to match the page. A crawler that renders the page, or a user who shares it, gets signals that describe the actual content rather than a single generic shell.

Updating title and description

The most basic per-page signals are the title and meta description, and both can be set from script.

Setting head tags per route

function setMeta(title, description) {
  document.title = title;
  var tag = document.querySelector('meta[name="description"]');
  if (!tag) {
    tag = document.createElement("meta");
    tag.setAttribute("name", "description");
    document.head.appendChild(tag);
  }
  tag.setAttribute("content", description);
}

Setting the canonical per route

Each route should declare a canonical that points to its own URL, not the homepage.

Repointing the canonical link

function setCanonical(url) {
  var link = document.querySelector('link[rel="canonical"]');
  if (!link) {
    link = document.createElement("link");
    link.setAttribute("rel", "canonical");
    document.head.appendChild(link);
  }
  link.setAttribute("href", url);
}

A correct canonical prevents many routes from collapsing into one in the index, a point covered in SPA indexing.

Updating structured data

Structured data should describe the page currently shown, so it is replaced on navigation.

Swapping the JSON-LD block

function setSchema(obj) {
  var el = document.getElementById("ld");
  if (!el) {
    el = document.createElement("script");
    el.type = "application/ld+json";
    el.id = "ld";
    document.head.appendChild(el);
  }
  el.textContent = JSON.stringify(obj);
}

Why prerendering still helps

Updating tags in script works, but serving rendered HTML up front is more reliable.

Rendered HTML versus rendered-on-the-client

Search engines can render JavaScript, but rendering is deferred and not guaranteed for every page on every crawl. Sending finished HTML, through server-side rendering or prerendering, means the title, content, and structured data are present in the initial response, with no dependence on the crawler executing your script. The client-side updates in this example then keep everything correct during navigation. The trade-offs are explained in rendering for search engines.

Common pitfalls

JavaScript SEO fails in a few specific ways.

What usually goes wrong

The most common is leaving every route with the same title and description from the static shell, so pages look duplicate. The second is a canonical that points to the homepage on every route, collapsing the site in the index. The third is loading important content only after a user interaction, which a crawler never performs, so it never sees that content. The deeper mechanics are in JavaScript rendering and indexing.

Frequently Asked Questions

How do I set per-page titles in a single page application?

Update document.title on each route change, and create or update the meta description tag the same way. Each route should set signals that describe its own content rather than reusing the static shell's defaults.

Why set a canonical on every route?

Without a per-route canonical, many client-rendered routes can collapse into one URL in the index. Repointing the canonical link to the current route's URL tells search engines each page is distinct.

Is prerendering necessary if Google renders JavaScript?

It is not strictly required but it is more reliable. Rendering is deferred and not guaranteed every crawl, so serving finished HTML puts your title, content, and structured data in the initial response regardless.

Why is my JavaScript content not indexed?

Common causes are duplicate titles across routes, a canonical pointing to the homepage everywhere, or content that loads only after a user interaction a crawler never performs. Each leaves the page looking empty or duplicate.

Read next: the realtime UI example, or back to the Examples hub.

Want the full picture?

The JavaScript SEO cluster covers crawling, rendering, and indexing in depth.

Read: JavaScript SEO →