10. Search Engine Optimization

Many decisions that determine whether a page is found are made by front-end developers: which HTML is served, how links are marked up, whether content needs JavaScript, how fast the page loads and what metadata travels with it.Search engine optimization (SEO) is full of folklore, so this chapter says whether each factor is confirmed by the search engines, denied or simply unknown.The chapter starts with the machinery of crawling, ranking and AI answers, then works through the classic factor families (domain, page, backlinks, navigation and brand), SEO tools and microformats. It closes with technical SEO for JavaScript sites and generative engine optimization. Meta tags, JSON-LD, sitemaps and robots.txt (Section 2.11) and Core Web Vitals (Section 9.10) are not repeated.What you will learn:
  • How crawling, rendering, indexing and ranking work at Google and Bing

  • Which ranking factors are confirmed, which are myths, and which are unknown

  • How links, duplicates, translations and redirects affect a site

  • Which SEO tools, microformats and structured data formats to use

  • How to make JavaScript sites visible to search engines and AI assistants


  • 10.1 How Search Engines Work

    Every web search engine discovers URLs, fetches and renders them, stores what it understood in an index, and ranks indexed pages for each query. Google and Bing now also write AI answers that cite some of those pages. Knowing where a page can drop out of this pipeline is the most useful SEO skill a developer can have.

    10.1.1 Crawling, Rendering and Indexing

    Google describes Search as crawling, indexing and serving, with a rendering phase for JavaScript between crawling and indexing. Figure 10.1 shows the pipeline and where pages drop out.Figure 10.1: The crawl, render, index and serve pipeline, and where pages drop outDiscovery and crawling. Crawlers find URLs in links on known pages, in sitemaps (Section 2.11.4) and in their crawl history. Google only discovers links written as <a href>; a <span onclick> that sets location is invisible to it. Bing, Yandex, Naver, Seznam.cz and Yep also accept IndexNow pings, which share a changed URL with every participating engine; Google is not listed as a participant. The crawler reads robots.txt first. Most Google fetches come from Googlebot Smartphone, because Google indexes the mobile version of a site; Googlebot reads only the first 2 MB of an HTML, CSS or JavaScript file (64 MB for PDFs).Rendering. Google renders with an evergreen (regularly updated) Chromium, and Bingbot has rendered with Microsoft Edge's engine since Bing's 2019 announcement. A fetched page waits in a render queue "for a few seconds, but it can take longer than that". If the initial HTML contains noindex or the status is not 200, Google may skip rendering altogether, so a script that later removes noindex never runs. Section 10.9.2 covers JavaScript rendering in depth.Indexing. The indexer extracts text, links and structured data, groups near-duplicates into a cluster and picks one canonical URL for it (Section 10.3.15). Google doesn't guarantee that any page will be crawled, indexed or served. Under the hood: A search index is an inverted index: for every term it stores a posting list of the documents containing it, usually with word positions. A query intersects a few posting lists instead of scanning pages, which is what makes a lookup across billions of documents fast. Use Search Console's URL Inspection tool (Section 10.3.14) to see the HTML Google rendered, keep essential content and links in server-rendered HTML where you can, and never block the CSS and JavaScript a page needs to render.

    10.1.2 Ranking Systems: PageRank to Helpful Content and E-E-A-T

    Google's founding idea, in Sergey Brin and Lawrence Page's 1998 paper "The Anatomy of a Large-Scale Hypertextual Web Search Engine", was to treat a link as a vote weighted by the importance of the page casting it. PageRank models a "random surfer" who follows links and, with probability 1 - d, jumps to a random page; the paper usually set the damping factor d to 0.85. Listing 10.1 computes it for four pages.

    const links = { A: ['B', 'C'], B: ['C'], C: ['A'], D: ['C'] };

    const n = 4, d = 0.85;

    let pr = { A: 1 / n, B: 1 / n, C: 1 / n, D: 1 / n };

    for (let i = 0; i < 50; i++) {

    const next = { A: (1 - d) / n, B: (1 - d) / n, C: (1 - d) / n, D: (1 - d) / n };

    for (const [from, outs] of Object.entries(links)) {

    for (const to of outs) next[to] += (d * pr[from]) / outs.length;

    }

    pr = next;

    }

    console.log(Object.entries(pr).map(([p, v]) => `${p}=${v.toFixed(3)}`).join(' '));

    Listing 10.1: PageRank by power iteration A=0.373 B=0.196 C=0.394 D=0.038 Output of Listing 10.1 A and B each have one inbound link, but A's comes from C, the most important page, which passes its whole vote to A; B gets only half of A's vote. D, with no inbound links, keeps only the random-jump share.Google's ranking systems guide still lists PageRank, "one of our core ranking systems used when Google first launched", among many other systems it now documents by name. Table 10.1 shows the milestones. Year System What it does Status in 2026 1998 PageRank Weights pages by links Active, one of many link systems 2011 Panda Demotes low-quality content Part of core since 2015 2012 Penguin Fights link spam Part of core since 2016 2013 Hummingbird Major ranking overhaul Retired; systems evolved since 2015 RankBrain Relates words to concepts Active 2018 Neural matching Relates queries to pages Active 2019 BERT Understands word combinations Active 2021 MUM Understands and generates language Specific uses, not general ranking 2022 Helpful content Rewards people-first content Part of core since March 2024 Table 10.1: Milestones in Google's documented ranking systems Other active systems cover freshness, deduplication, passages, reviews, site diversity and spam (SpamBrain). Google retunes them together in broad core updates "several times a year".E-E-A-T stands for experience, expertise, authoritativeness and trustworthiness, and Google calls trust the most important of the four. Quality raters assess it using Google's rater guidelines, but "search raters have no control over how pages rank." Google states that "E-E-A-T itself isn't a specific ranking factor"; its systems use a mix of signals that tend to identify such content, weighted more heavily for "Your Money or Your Life" (YMYL) topics such as health and finance. Warning: There is no E-E-A-T score to optimize. Author boxes, credentials and "About" pages help readers judge trust, but Google has not confirmed that adding them raises rankings. Treat any tool that reports an "E-E-A-T score" as a guess.

    10.1.3 AI Overviews, AI Mode and Answer Engines

    An answer engine replies with generated prose that cites web pages, instead of only a list of links. Google runs two such features on its index:
  • AI Overviews, an AI-written summary above the results, rolled out to all US users in May 2024 and reached more than 200 countries and territories by May 2025. Google reported over 2.5 billion monthly active users in May 2026.

  • AI Mode, a conversational search for comparisons and multi-step questions, started as a Labs experiment in March 2025 and expanded to over 200 countries and territories in October 2025. It passed 1 billion monthly active users by May 2026, and in July 2026 Google said it had brought AI Overviews and AI Mode "together into one seamless Search experience".

  • Both may use query fan-out: the model runs many related searches across subtopics and writes an answer from the pages they return. ChatGPT search, Microsoft Copilot and Perplexity are answer engines too. Bing's search team puts the difference plainly: "Search optimizes for likelihood of relevance. Grounding must measure strength of evidence."What Google documents. There are "no additional requirements to appear in AI Overviews or AI Mode": a page must be indexed and eligible to show a snippet. You "don't need to create new machine readable files, AI text files, or markup". Traffic from AI features is counted inside the Web search type of Search Console's Performance report. Blocking the Google-Extended token "does not impact a site's inclusion in Google Search."What it does not. Google does not explain how pages are chosen for citation beyond eligibility and publishes no click-through figures in its guide, only the claim that these clicks are "higher quality". Any list of "AI Overview ranking factors" is inference.Besides noindex, the documented controls are the snippet rules: nosnippet and max-snippet also limit use of your text as direct input to AI Overviews and AI Mode, and data-nosnippet hides a span, div or section.

    <meta name="robots" content="max-snippet:160">

    <p>Our review covers battery life, price and repairability.</p>

    <div data-nosnippet>Subscriber-only benchmark tables and conclusions.</div>

    Listing 10.2: Limiting what AI features and snippets may quote Tip: Text hidden from AI answers is also hidden from ordinary snippets. Section 10.10 covers AI crawlers, robots.txt controls and measuring visibility in AI answers.

    10.1.4 Market Share

    Worldwide, Google sends roughly nine in ten search visits, but national markets differ. Table 10.2 uses StatCounter, which counts clicks from search engines to the sites running its tracker, not the queries themselves. Engine Worldwide Key national market Google 89.60% United States 84.83% Bing 5.45% United States 10.04% Yahoo! 1.44% United States 2.77% Yandex 1.29% Russia 69.94% DuckDuckGo 0.79% United States 1.81% Baidu 0.45% China 47.29% Naver 0.28% South Korea 45.05% Table 10.2: Search engine market share, all platforms (StatCounter, September 2026 month to date) Bing is a desktop engine: it sent 13.85% of desktop search visits worldwide but 0.72% on mobile, where Google held 95.47%. Local engines win at home: Yandex leads Russia, Baidu leads China (with Bing second at 22.21%), and Naver runs neck and neck with Google in South Korea. DuckDuckGo says it "largely" sources its traditional links from Bing.Register every site with Google Search Console and Bing Webmaster Tools. For China, Russia or Korea, also use Baidu Search Resource Platform (https://ziyuan.baidu.com/), Yandex Webmaster (https://webmaster.yandex.com/) or Naver Search Advisor (https://searchadvisor.naver.com/). Shares move by a point or more a month, so check StatCounter (https://gs.statcounter.com/search-engine-market-share) for your audience's country.

    10.2 Domain Factors

    Domain factors describe a whole site: its name, history, hosting, uptime and security. Google's ranking systems "work on the page level", but "site-wide signals and classifiers are also used." Much domain folklore comes from patents and correlation studies rather than Google's documentation, so the table sorts each factor by what Google Search Central says today. Factor Status in Google's documentation What to do Domain age Unconfirmed Ignore when buying a domain Keywords in the domain Confirmed, very small Choose a memorable brand Registration length, WHOIS privacy Unconfirmed Decide on cost and privacy Country-code TLD Confirmed, for country targeting Use only when serving one country Server location Confirmed, weak Host wherever is fastest Domain Authority, TrustRank scores Not a ranking factor Earn real links and mentions Uptime Confirmed: failing URLs drop out Keep outages short, send 503 HTTPS Not a direct factor today Serve HTTPS anyway Hacked content Confirmed: warnings or removal Monitor and fix fast Table 10.3: Domain factors and what Google confirms

    10.2.1 Age

    A 2003 Google patent, "Information retrieval based on historical data" (US 7,346,839), describes scoring a document by its "inception date", possibly the date its domain was registered. A patent shows what Google invented, not what it runs, and no Search Central page lists domain age: it is unconfirmed. Age mostly stands in for the content and links a site has had time to earn.Buying an old domain for its history can backfire: Google's expired domain abuse policy targets expired domains "repurposed primarily to manipulate search rankings". Check a used domain's past in the Wayback Machine (web.archive.org (https://web.archive.org/)) first.

    10.2.2 Name

    Google counts "the words in domain names as one of many factors", but its exact match domain system stops names like best-places-to-eat-lunch.com from getting "too much credit". The SEO Starter Guide says such keywords "alone have hardly any effect beyond appearing in breadcrumbs", and that Google "doesn't care which TLD you're using" unless you target one country. Choose a short, memorable brand people will search for by name (Section 10.6.4).

    10.2.3 Ownership

    The same patent notes that legitimate domains "are often paid for several years in advance, while doorway (illegitimate) domains rarely are used for more than a year." Registration length, WHOIS privacy and ownership changes remain unconfirmed. What Google does confirm is the site reputation abuse policy against third-party pages placed on a site "mainly because of that host's already-established ranking signals". Hosting guest content is fine; renting out your domain's standing is not.

    10.2.4 Geolocation

    To find a page's target country, Google weighs a country-code TLD such as .de ("a strong signal"; vanity ccTLDs like .tv and .me count as generic), hreflang (Section 10.3.11), server location ("not a definitive signal", given CDNs), and local addresses, currency and links. It "ignores locational meta tags (like geo.position...)". Pick a ccTLD only for a single-country business, and never redirect by IP address: most Googlebot crawls come from the US, so it would see only one version.

    10.2.5 Authority and TrustRank

    TrustRank comes from a 2004 Stanford and Yahoo! paper, "Combating Web Spam with TrustRank": humans label a seed set of trusted pages and trust flows outward along links. Google holds a patent titled "Search result ranking based on trust" (US 7,603,350), but nothing shows it runs a TrustRank score: unconfirmed. "Domain Authority" (a 1-100 score from Moz) and similar vendor metrics are estimates that Moz itself says are "not a direct ranking factor". Google confirms link analysis systems, including PageRank (Section 10.1.2), and systems that "surface more authoritative pages". Use vendor scores to compare sites, never as a goal.

    10.2.6 Site Uptime

    Downtime is a confirmed risk. On 5xx or 429 responses Googlebot slows down, and indexed URLs "are preserved in the index, but eventually dropped." Google advises a 503 status for an outage of a day or two, never a 503 for robots.txt (that halts all crawling), and a 200 placeholder page for longer closures.

    server {

    listen 443 ssl; # certificates as in Section 1.6.5

    server_name example.com;

    root /var/www/maintenance; # holds maintenance.html and robots.txt

    add_header Retry-After 7200 always;

    error_page 503 /maintenance.html;

    location = /robots.txt { try_files $uri =404; }

    location = /maintenance.html { internal; }

    location / { return 503; }

    }

    Listing 10.3: Maintenance mode that asks crawlers to retry in two hours

    10.2.7 SSL Certificate

    In August 2014 Google made HTTPS "a very lightweight signal", "affecting fewer than 1% of global queries." Today's page experience documentation lists secure serving as a good practice but says "beyond Core Web Vitals, other page experience aspects don't directly help your website rank higher": HTTPS is not a direct factor now. Serve it anyway: Chrome has labeled HTTP pages "Not secure" since version 68 (2018), many web APIs need a secure context, and Let's Encrypt is free (Section 1.6.5). When migrating, 301-redirect every HTTP URL to its HTTPS twin.

    10.2.8 Hacked Site

    Hacked content is confirmed harm: affected pages "can appear with a warning label in search results or an interstitial warning page in the browser", and Google may remove a site entirely. On November 30, 2018 even Search Engine Land vanished from Google for over 12 hours when Google's hacked-site detection misfired. After cleaning up, choose Request Review in Search Console's Security issues report. Injected spam is often served only to Google, so check with the URL Inspection tool or a crawler-like request:

    curl -s -A "Googlebot/2.1 (+http://www.google.com/bot.html)" \

    -e "https://www.google.com/" https://example.com/ | grep -icE "casino|viagra|loan"

    Listing 10.4: Looking for spam that is shown only to visitors from Google A non-zero count on a site that never uses those words signals injected content; see web.dev/articles/hacked (https://web.dev/articles/hacked).

    10.3 Page Factors

    Page factors are the part of SEO a front-end developer controls directly: markup, URLs, words, speed, links and layout. Google's How Search Works site states the simplest one outright: if a query's keywords "appear in the headings or body text of a webpage, the information might be more relevant." The table sorts the common claims about the rest. Claim Status Google's position Keywords in headings and body text Confirmed "The most basic signal" Keywords meta tag, meta description Not a ranking factor Keywords ignored; description feeds snippets Heading order and count Not a ranking factor "Doesn't matter" Keywords in URL Confirmed, very small "Hardly any effect" LSI keywords, valid HTML Unconfirmed Never documented Word count Not a ranking factor "No, we don't" prefer one Keyword stuffing, hidden text Confirmed demotion Spam policies Helpful, people-first content Confirmed Core ranking systems Core Web Vitals Confirmed Used by ranking systems Mobile version of the page Confirmed Mobile-first indexing Fresh content Confirmed for some queries Freshness systems Internal links and anchor text Confirmed Link analysis systems Sitemap priority and changefreq Not a ranking factor Ignored Installing Analytics or Search Console Unconfirmed No documented link Duplicate URLs on your own site Not a ranking factor No penalty Intrusive interstitials, top-heavy ads Confirmed 2012 layout and 2017 mobile updates Table 10.4: Page factors and what Google confirms

    10.3.1 HTML

    The <title> element is the main source of the title link, but Google also uses "the main visual title", headings, prominent text and anchor text, and replaces stuffed or boilerplate titles. That keyword-first titles perform better is unconfirmed. Write one distinct, accurate title per page (Section 2.11.1).The meta description is not a ranking factor: in 2009 Google said "we still don't use the description meta tag in our ranking", and snippets are "primarily created from the page content itself".Heading order "doesn't matter" to Google, because "the web in general is not valid HTML", and validity itself is unconfirmed as a factor. Keep the <head> valid, though: canonical and hreflang links pushed into the body by a parsing error are ignored. For images, alt text is "the most important attribute", captions and nearby text help, and file names give "very light clues":

    <figure>

    <img src="/img/dalmatian-puppy-fetch.webp" alt="Dalmatian puppy playing fetch"

    width="800" height="533">

    <figcaption>Our 10-week-old Dalmatian, Pepper</figcaption>

    </figure>

    Listing 10.5: An image Google can understand Structured data (Section 2.11.3, Section 10.8) makes pages eligible for rich results; a direct ranking boost is unconfirmed.

    10.3.2 URL

    Keywords in a URL path have "hardly any effect beyond appearing in breadcrumbs", and studies showing short URLs rank higher are correlations, not causes. Google's URL guide gives usability rules instead: readable words rather than long IDs (/wiki/Aviation, not /index.php?topic=42&area=3a5e...), hyphens rather than underscores between words, and no # fragments to switch content, since "Google Search generally doesn't support URL fragments"; use the History API instead. Category folders such as /shoes/running/ help users and let Google learn which sections change often. Once published, keep URLs stable, or 301-redirect old ones.

    10.3.3 Keywords

    Query words in "the headings or body text" are Google's "most basic signal" of relevance, yet a searcher for "dogs" doesn't "want a page with the word 'dogs' on it hundreds of times". Keyword stuffing and hidden text (white on white, off-screen CSS) violate the spam policies. Keyword-density targets, the "first 100 words" rule, TF-IDF formulas and "LSI keywords" are unconfirmed: Google's documentation never mentions them. It documents RankBrain and neural matching, which relate words to concepts (Section 10.1.2), so write the words and synonyms your readers use.

    10.3.4 Content

    Length is not a ranking factor: "Are you writing to a particular word count because you've heard or read that Google has a preferred word count? (No, we don't.)" What Google confirms is a preference for helpful, people-first content, part of its core ranking systems since March 2024: original information, clear sourcing, visible expertise and no easily verified errors. Topics that affect health or finances ("Your Money or Your Life") get "even more weight" on E-E-A-T (Section 10.1.2).Generated content is judged by value, not by tool. Generative AI "can be particularly useful when researching a topic", but the scaled content abuse policy covers many low-value pages made to manipulate rankings "no matter how it's created". Quality raters' "ratings don't directly influence ranking." Moderate user comments and mark their links rel="ugc" (Section 10.4.5).

    10.3.5 User-friendliness

    Google weighs the "relevance and usability of pages", and its page experience checklist asks whether visitors "can easily distinguish the main content from other content". Beyond Core Web Vitals, though, such aspects "don't directly help your website rank higher"; they help by satisfying visitors. Bounce rate and time on site as direct factors are unconfirmed (Section 10.5.3).Content in tabs or accordions is fine if it is in the HTML, but Google "won't load content that requires user interactions (for example, swiping, clicking, or typing)". A clear hierarchy aids crawling (Section 10.3.9), and BreadcrumbList structured data can show the trail in desktop results.

    10.3.6 Loading Speed and Core Web Vitals

    Speed is the one page experience aspect Google confirms: "Core Web Vitals are used by our ranking systems." "Good" means LCP within 2.5 s, INP under 200 ms and CLS under 0.1, measured on real Chrome users (the CrUX data behind Search Console's Core Web Vitals report); Section 9.10 shows how to measure and fix each.Keep expectations realistic. Google "always seeks to show the most relevant content, even if the page experience is sub-par", and good scores don't "guarantee that your pages will rank at the top". Aim for "good" for your users, not a perfect Lighthouse score for SEO.

    10.3.7 Mobile Usability

    On October 31, 2023 Google declared mobile-first indexing complete: it "uses the mobile version of a site's content ... for indexing and ranking." Content missing from your phone layout is missing from Google, so serve the same content, structured data and metadata to every device, ideally with responsive design, "the easiest design pattern to implement and maintain."Two old claims need updating. After "Mobilegeddon" (2015) Google called mobile-friendliness a ranking signal; its page experience documentation now says aspects beyond Core Web Vitals "don't directly help". And AMP has not been required for the Top Stories carousel since 2021.

    10.3.8 Freshness

    Google runs "query deserves freshness" systems: a search for a just-released movie or a recent earthquake favors recent pages, while evergreen queries do not. Freshness is therefore confirmed for time-sensitive queries only. Publishing frequency as a site-wide boost is not a ranking factor; Google asks whether you are adding or removing content to seem "fresh" and answers "(No, it won't)", and it treats "changing the date of pages to make them seem fresh when the content has not substantially changed" as a warning sign.Update pages when facts change, show a visible "Published" or "Last updated" date, and keep dateModified in structured data and <lastmod> in the sitemap honest (Section 10.3.13).

    10.3.9 Links

    Google "uses links as a signal when determining the relevancy of pages and to find new pages to crawl", but reliably follows only <a> elements with an href. Descriptive anchor text ("ghost peppers", not "click here") helps people and Google, and "every page you care about should have a link from at least one other page on your site."Linking out is safe: external links "can help establish trustworthiness (for example, citing your sources)." That followed outbound links "leak" PageRank is unconfirmed, as is any ranking effect of broken links. Use nofollow only for sources you distrust and sponsored for paid links (Section 10.4.5).

    10.3.10 Side Content

    Supplementary content (related links, calculators, author bios, contact and policy pages) earns readers' trust. Google's helpful content questions look for "clear sourcing, evidence of the expertise involved, background about the author or the site", such as "an author page or a site's About page", but no document makes an About, Terms or Privacy page a factor in itself: unconfirmed. Claims that embedded YouTube videos or other multimedia boost rankings are unconfirmed too. Add these features because visitors use them.

    10.3.11 Translations and hreflang

    Translated pages are not duplicates: localized pages "are only considered duplicates if the main content of the page remains untranslated." Declare versions with hreflang in the HTML head, an HTTP Link header or the sitemap (all equivalent). Each version lists itself and all others with absolute URLs, or unreciprocated links are ignored. Codes are an ISO 639-1 language plus an optional ISO 3166-1 region (de-CH); a region alone is invalid, and en-UK does nothing (the code is GB). Google detects language itself and does not use hreflang or lang for that.

    <link rel="canonical" href="https://example.com/de/preise">

    <link rel="alternate" hreflang="en" href="https://example.com/en/pricing">

    <link rel="alternate" hreflang="de" href="https://example.com/de/preise">

    <link rel="alternate" hreflang="de-CH" href="https://example.com/ch/preise">

    <link rel="alternate" hreflang="x-default" href="https://example.com/">

    Listing 10.6: The head of the German page in a set of four Each version canonicalizes to itself, and x-default names the fallback, such as a language picker. Auto-translating scraped content into many pages is scaled content abuse. Section 10.9.4 covers multi-country structures.

    10.3.12 WordPress Tags

    WordPress runs 40.3% of all websites (W3Techs, September 2026), so its habits matter. Each tag creates an archive page such as /tag/seo/ listing matching posts. Google has no WordPress-specific signal; tags help only when their archives are useful to readers. A tag used once produces a thin, near-duplicate page, and since WordPress 5.5 (2020) the built-in sitemap at /wp-sitemap.xml lists tag archives by default. Use a few well-chosen tags, or drop tag archives from the sitemap:

    add_filter('wp_sitemaps_taxonomies', function ($taxonomies) {

    unset($taxonomies['post_tag']);

    return $taxonomies;

    });

    Listing 10.7: Removing tag archives from WordPress's core sitemap (functions.php) Section 10.7.11 compares SEO plugins that manage archives without code.

    10.3.13 Sitemap

    A sitemap aids discovery, not ranking. Google "ignores <priority> and <changefreq> values" and trusts <lastmod> only when "consistently and verifiably" accurate. Use absolute canonical URLs, at most 50,000 per file (50 MB), and keep the file at the root, because a sitemap covers only its own directory unless submitted in Search Console (Section 2.11.4). The script escapes & as &amp;.

    import { writeFileSync } from 'node:fs';

    const site = 'https://example.com';

    const pages = [

    { path: '/', updated: '2026-09-01' },

    { path: '/product?id=45&lang=en', updated: '2026-07-30' },

    ];

    const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;');

    const urls = pages.map((p) => ` <url><loc>${esc(site + p.path)}</loc>`

    + `<lastmod>${p.updated}</lastmod></url>`);

    const xml = ['<?xml version="1.0" encoding="UTF-8"?>',

    '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">', ...urls, '</urlset>'];

    writeFileSync('sitemap.xml', xml.join('\n') + '\n');

    console.log(xml.join('\n'));

    Listing 10.8: build-sitemap.mjs: generating sitemap.xml with Node.js

    <?xml version="1.0" encoding="UTF-8"?>

    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

    <url><loc>https://example.com/</loc><lastmod>2026-09-01</lastmod></url>

    <url><loc>https://example.com/product?id=45&amp;lang=en</loc><lastmod>2026-07-30</lastmod></u

    rl>

    </urlset>

    Output of Listing 10.8 Take lastmod from real content changes (a CMS field or git log), never from build time.

    10.3.14 Using Google Analytics and Google Search Console

    That installing Google Analytics or verifying Search Console helps indexing or rankings is unconfirmed, with no support in Google's documentation. Pages "can be discovered by Google" without submitting a sitemap, and sites rank without either tool.Use both for what they measure. Search Console shows Google's view (queries, clicks, impressions, average position, indexing, Core Web Vitals, security issues and manual actions) and emails you about new issues. Google Analytics 4 shows what visitors do; Universal Analytics stopped processing data on July 1, 2023. Section 10.7.1 tours both.

    10.3.15 Duplicate Content and Canonical URLs

    The "duplicate content penalty" for your own site is a myth: "If you have some content that's accessible under multiple URLs, it's fine; don't fret about it." Google clusters the copies, picks one canonical URL and consolidates signals, though duplicates waste crawling. Copying other sites differs: scraping breaks spam policy, and a "significant volume of valid copyright removal requests" demotes a site's other content.Figure 10.2: Duplicate URLs collapse into one canonical URLHints stack, so keep them consistent: a self-referencing canonical on every page, internal links to the canonical form, absolute URLs, and an HTTP header for non-HTML files:

    <link rel="canonical" href="https://example.com/green-dress">

    Link: <https://example.com/downloads/white-paper.pdf>; rel="canonical"

    Listing 10.9: Canonical hints in HTML and in an HTTP response header Never canonicalize with robots.txt or noindex, and don't let JavaScript change the canonical link. Search Console's URL Inspection tool shows the Google-selected canonical, which can differ from yours.

    10.3.16 Excessive Monetization

    Ads are allowed; blocking the content is not. Google's 2012 page layout algorithm hit pages giving "a large fraction of the site's initial screen real estate to ads", but not ads above the fold "to a normal degree". Since January 10, 2017, mobile pages hidden behind intrusive interstitials "may not rank as highly"; cookie notices, age gates and "banners that use a reasonable amount of screen space" are exempt.Figure 10.3: An intrusive interstitial versus a small, dismissible bannerAffiliate sites are fine too: "Not every site that participates in an affiliate program is a thin affiliate." Thin affiliation, copied merchant text "without any original content or added value", is spam. Mark affiliate links rel="sponsored", never cloak them with sneaky redirects, and see Section 10.7.12 on ad networks.

    10.4 External Backlink Factors

    Links from other sites were the raw material of PageRank (Section 10.1.2), and Google's ranking systems guide still lists "link analysis systems and PageRank". What has aged badly is the folklore built on that fact. Google now documents which links you must label and which count as spam, and says SpamBrain neutralizes unnatural links. Each claim below is marked confirmed, not a factor or unconfirmed. Internal links are covered in Section 10.3.9.

    10.4.1 The Linker's Domain

    Confirmed: PageRank weighs a link by the importance of the page casting it, so one link from a respected site outweighs dozens from obscure ones. Google publishes no domain "trust" score (Section 10.2.5). Unconfirmed: that referring-domain counts cause rankings (vendor studies show only correlation), that .edu, .gov or country-code domains pass a bonus, and that old domains or old links pass more. The advice to get links from different "Class C" IP blocks dates from link networks hosted on one server; classful addressing ended with CIDR in 1993, Google documents no IP-based link grouping, and most sites now share CDN addresses.

    10.4.2 The Linker's Content

    Google's link systems use links "to determine what pages are about", so links from relevant pages in your field are the ones it interprets best. The claim that a link in a 1,000-word post beats one in a short snippet is unconfirmed; Google says "the length of the content alone doesn't matter for ranking purposes." Main-content links outweighing footer links is plausible but unpublished. Wikipedia marks external links rel="nofollow", so a citation there brings readers, not reliable ranking credit.

    10.4.3 Number of Links

    Link counts are not additive. In the PageRank formula a page divides its vote among its outbound links, so a page with 300 external links passes a sliver of what a page with three passes. Sitewide footer or blogroll links are many links from one source, and footer links planted across many sites are named link spam. Link velocity, a boost for a rising link count and a penalty for a falling one, is unconfirmed; a sudden surge of bought links is what SpamBrain is built to catch.

    10.4.4 Linking Text

    Anchor text is confirmed: Brin and Page attached link text to the target page because "anchors often provide more accurate descriptions of web pages than the pages themselves." Google asks for anchors that are "descriptive, reasonably concise, and relevant" and uses an image link's alt text as its anchor. Exact-match keyword anchors are a risk: "links with optimized anchor text" in guest posts and press releases are named spam. Weight from the link title attribute is unconfirmed. Pharmacy or casino phrases in Search Console's Top linking text table can signal a hack or spam attack.

    10.4.5 nofollow, sponsored and ugc

    rel="nofollow" was proposed in 2005 against comment spam, and Google originally ignored such links. On 10 September 2019 Google added sponsored and ugc and made all three "hints about which links to consider or exclude", for ranking at once and for crawling and indexing from 1 March 2020. Value Use it for Google's treatment sponsored Ads and paid placements Hint; preferred for paid links ugc Comments and forum posts Hint nofollow Links you don't vouch for Hint; also accepted for paid links Table 10.5: Link qualification attributes

    <a href="https://shop.example/" rel="sponsored">Partner offer</a>

    <a href="https://visitor.example/" rel="ugc nofollow">Commenter's site</a>

    Listing 10.10: Qualifying outbound links (values combine) Use nofollow "only when you don't trust the source, and not for every external link"; nofollowing everything to hoard PageRank goes against that advice. Qualified targets may still be crawled via sitemaps or other links, so use noindex to keep a page out of the index.

    10.4.6 Redirection

    Redirects are normal for site moves and retired URLs. Google treats a permanent redirect (301, 308) as a signal that the target should be canonical and documents no PageRank loss, so redirect old URLs permanently to their closest replacement to keep their links (Section 10.3.15). The spam policies forbid sneaky redirects, which show "users and search engines different content", such as redirecting only mobile users to a spam domain. Doorway abuse, pages built to rank for similar queries and funnel visitors elsewhere, is a separate violation.

    10.4.7 Dubious Links

    Google defines link spam as "creating links to or from a site primarily for the purpose of manipulating search rankings". The old warnings about directories, comments, press releases and widgets are now written policy: Practice named as link spam Exception Buying or selling links, including for free products Qualified with sponsored or nofollow Excessive link exchanges; automated link building None Optimized anchors in guest posts, advertorials, press releases None Low-quality directory and bookmark sites None Keyword-rich links in widgets or cross-site footers None Optimized links in forum posts or signatures None Table 10.6: Practices in Google's link spam policy

    10.4.8 Unnatural Links

    Since the December 2022 link spam update, SpamBrain detects "sites buying links, and sites used for the purpose of passing outgoing links", and their credit is lost. Human reviewers add Unnatural links to your site or Unnatural links from your site manual actions in Search Console, so selling links is as risky as buying them. Google "works very hard" to stop third parties harming your site, and "most sites will not need" the disavow tool: use it only for a real or likely manual action, as a UTF-8 .txt file of URLs and domain:example.com lines. The Google Sandbox for new sites is denied: John Mueller said in 2021 that holding new pages back "is not the case".

    10.4.9 Links of Special Significance

    Extra weight for links to a homepage is unconfirmed. Links from real, editorially run sites beating fake blogs is confirmed in spirit, because SpamBrain targets sites that exist to pass links. Blog comments are content, not a ranking bonus, and their links belong under ugc. Guest posting reaches readers, but guest posts with optimized anchors are spam. The Google+ Circles claim is obsolete: consumer Google+ shut down on 2 April 2019. The durable strategy is to publish something others want to cite.

    10.5 Navigation Factors

    Old checklists credited RankBrain with watching how searchers behave; Google describes it as a system that relates words to concepts (Section 10.1.2). Behavior does matter, though. Google's How Search Works site states: "We also use aggregated and anonymized interaction data to assess whether search results are relevant to queries." Two events added detail, with limits worth stating precisely:
  • The US v. Google antitrust trial. Google's Pandu Nayak testified in 2023, as reported by Search Engine Land, that Navboost, dating back "at least to 2005", is "one of the important signals" and memorizes clicks on queries from the past 13 months. It cannot help pages that have no clicks.

  • The 2024 API documentation leak. Internal Content Warehouse API documentation published on GitHub in March 2024 surfaced in May. It names 14,014 attributes, including goodClicks, badClicks, lastLongestClicks and chromeInTotal. It "did not specify how any of the ranking features are weighted", nor which are used today; Google warned against assumptions "based on out-of-context, outdated, or incomplete information."

  • Signal Status Evidence Aggregated click and interaction data Confirmed Google's How Search Works Navboost click memory Confirmed under oath Nayak testimony, 2023 Personalization by search history Confirmed Google's How Search Works Long versus short clicks, pogo-sticking Unconfirmed weight Leaked attribute names only Chrome browsing data in ranking Unconfirmed Leaked attribute name only Analytics bounce rate, bookmarks Unconfirmed No Google documentation Table 10.7: Navigation signals and what is known about them

    10.5.1 Direct Traffic

    The claim that Google has "confirmed" using Chrome data to count site visits is wrong. Google has not documented it; the leaked chromeInTotal attribute is the closest evidence, and how it is used is unknown. What Google does document is the Chrome UX Report, real-user performance data behind Core Web Vitals (Section 9.10). Studies correlating direct traffic with rankings may simply reflect that popular brands get both. Treat direct visits, shown in GA4's Direct channel, as a sign of loyalty worth earning, not a ranking lever.

    10.5.2 Organic Click-through

    Search Console reports organic click-through rate (CTR) as clicks divided by impressions (Section 10.3.14). Clicks feed ranking in aggregate, but not as a raw "more clicks, higher rank" rule: a Google slide shown at the trial warned that ranking purely on clicks would "promote low-quality, click-bait results", so click data is weighed alongside other signals. A site-wide "quality score" from organic CTR is unconfirmed; Quality Score is a Google Ads metric. Earn clicks honestly with accurate titles and descriptions that the page then delivers on.Figure 10.4: Click data as described in the trial and the leak (weights unknown)

    10.5.3 Bounce Rate and Engagement

    In Google Analytics 4 a session is engaged if it lasts longer than 10 seconds, triggers a key event or views two or more pages; bounce rate is "the percentage of sessions that were not engaged." No Google documentation names Analytics bounce rate as a ranking signal, and Google cannot see it on sites without Analytics. Dwell time and pogo-sticking (returning to the results to click another listing) are a different matter: the leaked lastLongestClicks and badClicks attributes suggest Google classifies clicks this way, but thresholds and weights are unknown. A one-page visit that answers the question is a success, so optimize for satisfied visitors, not for a lower bounce number.

    10.5.4 Repeating Traffic

    Boosts for returning visitors or for pages bookmarked in Chrome are unconfirmed; Google has documented neither, and nothing public shows how Chrome data, if used, would be weighted. Returning visitors still matter commercially: they subscribe, buy and link. Build reasons to return, such as fresh content (Section 10.3.8), newsletters and useful tools.

    10.5.5 Search History

    Personalization is confirmed. Google says it uses "your location, past Search history, and Search settings to determine what is most relevant", so "football" shows the Chicago Bears in Chicago and the Premier League in London. The "About this result" panel shows whether search history influenced a result, and personalization can be switched off. Google also says Search "does not put people in 'filter bubbles'" and does not infer sensitive traits such as religion. The practical lesson: your own searches mislead you about rankings. Check average position in Search Console (Section 10.3.14) instead.

    10.5.6 Featured Snippets

    A featured snippet shows a passage from a page above its link. Google's documentation is blunt about requesting one: "How can I mark my page as a featured snippet? You can't." Its systems choose passages that answer the query, so clear headings that match questions, followed by a direct answer in the first sentence or a short list or table, give them good candidates; claims that length, HTTPS or authority decide selection come from vendor studies, not Google. Clicking a snippet scrolls the visitor to the quoted passage. To opt out, use nosnippet (all snippets), a low max-snippet (no guarantee) or data-nosnippet on specific text; nosnippet and max-snippet also limit use in AI Overviews (Section 10.1.3).

    <meta name="robots" content="max-snippet:50">

    <p data-nosnippet>Prices change daily; see the checkout page.</p>

    Listing 10.11: Capping snippet length and hiding one paragraph

    10.6 Brand Factors

    Big brands dominate many results pages, which has launched years of brand-signal theories. Google's documentation describes no brand score. It does describe systems that reward what strong brands tend to have: trusted links, clicks, reviews and a clear identity. The table sorts the claims. Brand claim Status Evidence Big brands get a direct boost Unconfirmed Google called Vince a trust change Several results from one site for brand queries Confirmed Site diversity exception Brand names in anchor text Unconfirmed Anchors describe the target only Unlinked brand mentions Unconfirmed for ranking Used for site names Brand search volume Unconfirmed No Google documentation Social followers and likes Unconfirmed Promotion value only Physical business location Confirmed for local results Business Profile ranking guide Table 10.8: Brand factors and what Google has confirmed

    10.6.1 Transactional Searches

    Shopping, travel and local queries often show product grids, flight boxes or maps instead of ten plain links, so the organic listings start lower. The belief that big brands got a blanket boost dates from the 2009 "Vince" change; Matt Cutts described it as a minor change factoring trust, quality and PageRank into generic queries, not a brand push. Brand and navigational queries behave differently: Google's site diversity system "generally won't show more than two web page listings from the same site", but may show more when "especially relevant", which is why a brand search can return a page of results from one domain.

    10.6.2 Brand Name in Anchor Text

    That brand-name anchors signal a "strong brand" is unconfirmed; Google documents anchor text only as a description of the target page (Section 10.4.4). Brand anchors are still what a natural link profile looks like, because people link to a company by its name or URL, and they avoid the keyword-optimized anchors Google names as spam. If your Search Console Top linking text is mostly exact commercial phrases rather than your name, someone has probably been building links.

    10.6.3 Brand Mentions

    Whether an unlinked mention of your brand helps ranking is unconfirmed. Google does confirm that its site name system, which picks the name shown beside each result, uses "references to it that appear on the web". For news publishers, Top Stories now has a documented, user-driven lever: a searcher can pick preferred sources, whose content is then "more likely to appear in Top Stories" with a "preferred" badge, and is also badged in AI Mode and AI Overviews. Only domain and subdomain sites qualify, and you may invite readers with a deep link. <a href="https://www.google.com/preferences/source?q=example.com">Add as preferred source</a> Listing 10.12: Inviting readers to add a preferred source

    10.6.4 Brand Searches

    The claims that searches for your brand prove you are "a real brand", and that searches for "brand + keyword" lift your ranking for the keyword alone, are unconfirmed. Neither Google's documentation nor the trial reporting describes such a mechanism. Brand searches are still valuable traffic from people who already know you. Track them by filtering the Search Console Performance report by queries containing your brand name, and watch the non-brand remainder to see whether your content wins new visitors.

    10.6.5 Social Media

    No Google documentation lists follower counts, likes or posting activity as ranking signals, so treat those claims as unconfirmed. Social media works indirectly: Google's SEO starter guide recommends "social media promotion" and calls word of mouth "one of the most effective and lasting ways" to build traffic, because people who discover you there may link, search for you and return. Tie your profiles to your site with Organization structured data on the home page; its sameAs property lists "your organization's profile page on a social media or review site" and helps Google "disambiguate your organization" (Section 2.11).

    <script type="application/ld+json">

    {

    "@context": "https://schema.org",

    "@type": "Organization",

    "name": "Example Widgets",

    "url": "https://www.example.com",

    "logo": "https://www.example.com/images/logo.png",

    "sameAs": ["https://www.linkedin.com/company/example", "https://github.com/example"]

    }

    </script>

    Listing 10.13: Organization structured data linking social profiles

    10.6.6 Brick-and-mortar Location

    For local results this factor is confirmed. Google's Business Profile guide says local results are "mainly based on relevance, distance, and popularity", and that prominence reflects "how many websites link to your business and how many reviews you have"; there is "no way to request or pay for a better local ranking." Claim and complete your Business Profile, keep name, address and phone consistent with your site, and ask customers for reviews. Whether Google checks for an office address to judge brand strength in ordinary web results is unconfirmed.

    10.7 SEO Tools

    Start with Google Search Console and Bing Webmaster Tools, which see data no third party can, then add one commercial suite. Prices are entry plans as listed on vendor pricing pages in September 2026; annual billing is often cheaper. Defunct tools are named at the end of each subsection.

    10.7.1 Google Tools

    Tool Use Cost Search Console Indexing, queries, disavow Free Analytics (GA4) Traffic and conversions Free; 360 is paid Tag Manager Deploy tracking tags Free Trends Relative search interest Free Keyword Planner Search volume ranges Google Ads account PageSpeed Insights Lighthouse plus field data Free Rich Results Test Structured data eligibility Free Table 10.9: Google's SEO tools See Section 10.3.14 for Search Console and Analytics, and Sections 1.3.3 and 9.10 for Lighthouse. The Mobile-Friendly Test was retired on December 1, 2023, and the Structured Data Testing Tool became the schema.org Schema Markup Validator in 2021 (Section 2.11.3). Use the disavow tool only for spammy links that have caused, or will likely cause, a manual action; Google says most sites never need it.

    10.7.2 Website Analysis

    Tool Strength Entry price Semrush All-in-one suite $139/month Moz Pro Domain Authority, audits $99/month Screaming Frog SEO Spider Desktop crawler Free to 500 URLs; £199/year Serpstat Budget suite $50/month Seobility Audits, rank tracking €49.90/month SpyFu Competitor keywords $39/month Netpeak Spider Desktop crawler $20/month Table 10.10: Site audit and competitive analysis tools Also online: Raven Tools ($49/month), SEO PowerSuite ($349/year), SEO Site Checkup ($39/month), BuzzSumo ($199/month, billed yearly), Similarweb (quote), cognitiveSEO, DashThis, Pitchbox, SERPWoo and SEO-Browser. DeepCrawl is now Lumar; ContentKing is now Conductor Monitoring. Keyword Eye is offline.

    10.7.3 Keyword Research

    Beyond Keyword Planner, KeywordTool.io (https://keywordtool.io/) mines autocomplete (free list; Pro $88/month), KWFinder is part of Mangools, Ubersuggest (limited free features) and AnswerThePublic come from Neil Patel, and Soovle (now SEO.com) is free. Longtail PRO and ANTRanks are gone.

    10.7.4 Rank Monitoring

    Search Console shows average positions free. Paid trackers follow chosen keywords by location and device: SE Ranking ($129/month), Advanced Web Ranking ($139/month), Nightwatch (€79/month, billed yearly), WooRank, RankWatch and Authority Labs. Alexa Rank is gone (alexa.com now points to Amazon's voice assistant), and checkpagerank.net redirects to SEOptimer.

    10.7.5 Linking

    Each backlink index runs its own crawler and sees a different subset of links. Ahrefs (https://ahrefs.com/) costs $129/month (free Ahrefs Webmaster Tools for your own sites), Majestic $49.99/month and LinkResearchTools from $5,799/year; Whitespark handles local citations, and Seobility and OpenLinkProfiler offer free checkers. Backlink Builder's site is blank. See Section 10.4 for which links help.

    10.7.6 Speed Test

    PageSpeed Insights pairs a Lighthouse lab run with real-user Chrome UX Report data (Sections 1.3.3 and 9.10). GTmetrix (https://gtmetrix.com/) adds test locations and monitoring: free to start, paid from $4.99/month billed yearly. Quick Sprout is now a blog with no speed test.

    10.7.7 Penalty Checkers

    Only Search Console's Manual actions report shows a Google penalty. "Penalty checkers" such as Semrush Sensor, AccuRanker's Google Grump and Algoroo measure ranking turbulence on tracked keywords, which tells you whether a drop matches an algorithm update. The Panguin tool and Website Penalty Indicator are offline.

    10.7.8 Plagiarism Detection

    Copyscape (https://www.copyscape.com/) finds copies of a page free (10 results); Premium checks cost 3 cents for 200 words plus 1 cent per extra 100. Its sister Siteliner finds duplicate content within your site. Also: Quetext (free to 1,000 words; $14.99/month), Grammarly (Pro $12/month), DupliChecker (from $5/week) and SmallSEOTools. PlagScan closed on January 1, 2026.

    10.7.9 Sitemap and robots.txt Tools

    Section 2.11.4 writes both files by hand. XML-Sitemaps.com crawls up to 500 URLs free, and MySitemapGenerator is free; the SEO Book and SmallSEOTools robots.txt generators still work, while LinkGraph's and Sure Oak's are gone. Check the result in Search Console's robots.txt report.

    10.7.10 Chrome Toolbar Extensions

    SEOquake (free, 1,000,000 users) audits pages and shows Semrush metrics; MozBar (free, 900,000 users) shows Moz's Domain and Page Authority. Both are vendor estimates, not Google data. Check My Links has left the Chrome Web Store.

    10.7.11 WordPress Plugins

    Active installs on WordPress.org: Yoast SEO 10+ million (Premium $118.80/year), Rank Math 4+ million (PRO from €7.99/month), All in One SEO 2+ million, XML Sitemap Generator for Google and Smush 1+ million each, W3 Total Cache 900,000+, The SEO Framework 200,000+ and AMP for WP 70,000+. Run only one SEO plugin.

    10.7.12 Ad Networks

    AdSense pays publishers 80% of AdSense for Content revenue after the advertiser platform's fee (about 68% for Google Ads buyers). Alternatives: Ezoic, Media.net, Monumetric (from 10,000 pageviews/month), Taboola and Revcontent (native), Amazon Associates, Skimlinks and Sovrn (affiliate), Infolinks, BuySellAds, BidVertiser and PopCash (pop-unders). PropellerAds' publisher side is now Monetag, ylliX merged into Advertica, and Adversal is offline.

    10.7.13 Survey Networks

    SurveyMonkey has a free Basic plan; Pollfish charges from $0.95 per panel response. Google Opinion Rewards (100M+ downloads) is the respondent side, paying up to $1.00 in Play credit per survey.

    10.7.14 Site Worth Estimators

    SiteWorthTraffic, SitePrice, Website Worth Checker and MyWorthWeb guess value from estimated traffic; they cannot see your revenue, so ignore them. MageNet is actually a contextual ad marketplace. GetWebPrice, UpSiteWorth and Websites Worth Calculator are offline.

    10.8 Microformats

    Microformats add meaning to HTML with class and rel values. A root class h-* marks an object; property prefixes tell parsers how to read values: p- text, u- URL, dt- date/time, e- embedded HTML. Parsers emit JSON (items, rels, rel-urls), so the page doubles as an API.Outputs below come from microformats-parser (https://github.com/microformats/microformats-parser) (MIT, npm install microformats-parser) via mf2(html, { baseUrl: 'https://example.com/' }), showing items[0], wrapped.

    10.8.1 h-adr, h-geo and h-card

    h-card describes a person or organization (p-name, u-url, u-email, u-photo, p-tel, p-org). h-adr holds an address (p-street-address, p-locality, p-postal-code, p-country-name) and h-geo WGS84 coordinates (p-latitude, p-longitude); both usually nest inside a card or event.

    <p class="h-card"><a class="p-name u-url" href="https://example.com/">Lip Phang</a>,

    <b class="p-adr h-adr"><i class="p-locality">Paris</i>, <i class="p-country-name">France</i></b>

    <b class="p-geo h-geo"><i class="p-latitude">48.86</i>, <i class="p-longitude">2.35</i></b></p>

    Listing 10.14: An h-card with a nested address and coordinates

    {"type": ["h-card"], "properties": {

    "name": ["Lip Phang"],

    "url": ["https://example.com/"],

    "adr": [{"type": ["h-adr"], "properties": {"locality": ["Paris"], "country-name":

    ["France"]},

    "value": "Paris, France"}],

    "geo": [{"type": ["h-geo"], "properties": {"latitude": ["48.86"], "longitude": ["2.35"]},

    "value": "48.86, 2.35"}]

    }}

    Output of Listing 10.14 Properties are arrays because they may repeat; nested objects also get a text value. Use explicit p-latitude and p-longitude: a lone <data class="p-geo h-geo" value="48.86;2.35"> yields no coordinates here.

    10.8.2 h-entry and h-feed

    h-entry marks a dated post: p-name, p-summary, e-content, dt-published, p-author, p-category, u-url, plus reply and like links (u-in-reply-to, u-like-of). Wrap a list of entries in h-feed (p-name, p-author, u-url, u-photo).

    <article class="h-entry"><h1 class="p-name">Microformats are amazing</h1>

    <p>By <a class="p-author h-card" href="/">Lip Phang</a>, <time class="dt-published"

    datetime="2026-09-16">16 Sep 2026</time></p>

    <div class="e-content"><p>Class names turn this page into an API.</p></div></article>

    Listing 10.15: A blog post as an h-entry with an embedded author h-card

    {"type": ["h-entry"], "properties": {"name": ["Microformats are amazing"],

    "author": [{"type": ["h-card"], "properties": {"name": ["Lip Phang"],

    "url": ["https://example.com/"]}, "value": "Lip Phang"}], "published": ["2026-09-16"],

    "content": [{"value": "Class names turn this page into an API.",

    "html": "<p>Class names turn this page into an API.</p>"}]}}

    Output of Listing 10.15 The relative href became absolute, and e-content kept text and markup. IndieWeb sites use this markup to show Webmention replies.

    10.8.3 h-event

    h-event uses p-name, p-summary, dt-start, dt-end, dt-duration, u-url, p-category and p-location (text, or a nested h-card or h-adr).

    <div class="h-event"><b class="p-name">HTML Meetup</b>, <span class="p-location">Berlin</span>:

    <time class="dt-start" datetime="2026-10-03T10:00">3 Oct, 10 am</time> to

    <time class="dt-end" datetime="2026-10-03T17:00">5 pm</time></div>

    Listing 10.16: An h-event

    {"type": ["h-event"], "properties": {"name": ["HTML Meetup"], "location": ["Berlin"],

    "start": ["2026-10-03T10:00"], "end": ["2026-10-03T17:00"]}}

    Output of Listing 10.16 dt- reads the datetime attribute: people see "5 pm", software gets ISO 8601.

    10.8.4 h-item, h-listing and h-product

    h-product offers p-name, u-photo, p-brand, p-category, e-description, u-url, u-identifier, p-review and p-price. h-item (p-name, u-url, u-photo) rarely stands alone; it names what an h-review reviews. h-listing is a draft.

    <div class="h-product"><img class="u-photo" src="/mfd.png" alt="">

    <b class="p-name">Microformats For Dummies</b> <data class="p-price" value="20.00">$20</data>

    from <a class="p-brand h-card" href="https://acme.example/">ACME</a></div>

    Listing 10.17: An h-product with a brand h-card

    {"type": ["h-product"], "properties": {"photo": ["https://example.com/mfd.png"],

    "name": ["Microformats For Dummies"], "price": ["20.00"], "brand": [{"type": ["h-card"],

    "properties": {"name": ["ACME"], "url": ["https://acme.example/"]}, "value": "ACME"}]}}

    Output of Listing 10.17 <data value> gives software a clean number while readers see "$20".

    10.8.5 h-recipe and h-resume

    h-recipe uses p-name, p-ingredient, p-yield, e-instructions, dt-duration, u-photo, p-author and p-nutrition. h-resume uses p-name, p-summary, p-contact (an h-card), p-education, p-experience, p-skill and p-affiliation. Both are drafts.

    <article class="h-recipe"><h2 class="p-name">Bagels</h2>

    <ul><li class="p-ingredient">Flour</li><li class="p-ingredient">Yeast</li></ul>

    <p>Takes <time class="dt-duration" datetime="PT1H">1 hour</time>,

    serves <data class="p-yield" value="4">four</data>.</p></article>

    Listing 10.18: An h-recipe with repeated ingredients

    {"type": ["h-recipe"], "properties": {"name": ["Bagels"], "ingredient": ["Flour", "Yeast"],

    "duration": ["PT1H"], "yield": ["4"]}}

    Output of Listing 10.18 dt-duration needs a valid duration string such as PT1H.

    10.8.6 h-review and h-review-aggregate

    h-review uses p-name, p-item, p-author, dt-published, p-rating, p-best, p-worst and e-content. h-review-aggregate summarizes many reviews with p-item, p-average, p-best, p-worst, p-count and p-votes (its spec is still a stub).

    <div class="h-review"><a class="p-item h-item" href="https://microformats.org/">Microformats</a>

    <data class="p-rating" value="5">5 stars</data> by <span class="p-author h-card">Joe Bloggs</span>

    <p class="p-summary">The simplest way to publish structured data.</p></div>

    Listing 10.19: An h-review of an h-item

    {"type": ["h-review"], "properties": {"item": [{"type": ["h-item"],

    "properties": {"name": ["Microformats"], "url": ["https://microformats.org/"]},

    "value": "Microformats"}], "rating": ["5"], "author": [{"type": ["h-card"],

    "properties": {"name": ["Joe Bloggs"]}, "value": "Joe Bloggs"}],

    "summary": ["The simplest way to publish structured data."]}}

    Output of Listing 10.19 Google's rich results read only JSON-LD, Microdata and RDFa, so review stars in search need schema.org markup (Section 10.8.9).

    10.8.7 rel-author, rel-enclosure, rel-license, rel-nofollow and rel-tag

    rel values describe the link itself. author, license, nofollow and tag are link types in the HTML Standard; enclosure marks a downloadable attachment such as a podcast file. Parsers collect them page-wide rather than inside an h-* object.

    <a rel="author" href="/about">Erin Smith</a>

    <a rel="license" href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>

    <a rel="tag" href="/tags/html">html</a>

    <a rel="enclosure" href="/talk.mp4">Download the talk</a>

    <a rel="nofollow ugc" href="https://spam.example/">A commenter's link</a>

    <a rel="me" href="https://social.example/@erin">Mastodon</a>

    <a rel="friend met co-worker" href="https://josh.example/">Josh</a>

    Listing 10.20: Link relations, including XFN values

    {"rels": {"author": ["https://example.com/about"],

    "license": ["https://creativecommons.org/licenses/by/4.0/"],

    "tag": ["https://example.com/tags/html"], "enclosure": ["https://example.com/talk.mp4"],

    "nofollow": ["https://spam.example/"], "ugc": ["https://spam.example/"],

    "me": ["https://social.example/@erin"], "friend": ["https://josh.example/"],

    "met": ["https://josh.example/"], "co-worker": ["https://josh.example/"]}}

    Output of Listing 10.20 Only rels is shown; rel-urls lists the same links by URL. Google's handling of nofollow and ugc is in Section 10.4.5.

    10.8.8 XFN

    XHTML Friends Network (XFN 1.1, by Tantek Çelik, Matt Mullenweg and Eric Meyer) describes people you link to: friendship (contact, acquaintance, friend), met, professional (co-worker, colleague), plus family, romantic and geographical values. Its lasting value is rel="me" ("this profile is also me"): when your Mastodon profile lists your site and the site links back with rel="me", Mastodon shows a checkmark.

    10.8.9 Microformats versus JSON-LD and Microdata

    Aspect Microformats2 Microdata JSON-LD Markup class and rel values itemscope, itemprop <script type="application/ld+json"> Vocabulary h-card, h-entry, ... Usually schema.org Usually schema.org Nesting Nested h-* roots Nested itemscope Plain JSON objects Google rich results Not a listed format Supported Supported, recommended Table 10.11: Three ways to embed structured data Google calls JSON-LD, Microdata and RDFa "equally fine" but recommends JSON-LD: it is easy to maintain, nests cleanly and can be injected by JavaScript. Use it for search features (Section 2.11.3), and add microformats for IndieWeb tools and rel="me"; both fit on one page.

    10.9 Technical SEO for JavaScript Sites

    A framework decides what HTML a crawler receives, and a crawler decides whether it will run your JavaScript to see more. Section 1.12.7 introduced the rendering strategies; this section looks at them from the crawler's side, then covers how Googlebot renders, how to test structured data (Section 2.11.3) and how to structure sites for several languages and countries.

    10.9.1 CSR, SSR, SSG and Hydration from a Crawler's Viewpoint

    Every crawler reads the initial HTML response. Only some go on to execute JavaScript and read the rendered DOM: Googlebot renders with an evergreen Chromium, Bingbot with Edge's engine (Section 10.1.1), and Applebot "may render the content of your website within a browser". Google still recommends server-side or pre-rendering because "not all bots can run JavaScript": a December 2024 Vercel and MERJ study found that GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot and Common Crawl's CCBot executed no JavaScript at all.Figure 10.5: What crawlers find in the response and after rendering, by rendering strategy
  • Client-side rendering (CSR). The shell has no text and no <a href> links, so Google finds nothing until the page leaves the render queue, and a non-rendering bot sees an empty page.

  • Server-side rendering (SSR) with hydration. Text, links, <title> and JSON-LD arrive in the response. Content fetched only after hydration, such as reviews loaded in useEffect, falls back to CSR rules.

  • Static site generation (SSG) and ISR. Every crawler gets complete HTML from a CDN; the risk is stale prices until the page is regenerated.

  • Dynamic rendering, prerendered HTML served only to bots, "was a workaround and not a long-term solution"; Google recommends SSR, static rendering or hydration instead.

  • Tip: Compare curl -s URL with chrome --headless --dump-dom URL: text that appears only in the second is invisible to non-rendering bots. Server-render the main content, internal links, canonical tag and structured data.

    10.9.2 How Googlebot Renders JavaScript

    Googlebot queues every URL that returns 200 for rendering unless a robots meta tag or header forbids indexing. When resources allow, the Web Rendering Service (WRS) runs the page in headless Chromium and parses the rendered HTML. In March 2026 Google dropped its old warning: it has rendered JavaScript "for multiple years now". What breaks sites are the differences from a user's browser: Browser feature In Google's WRS Scrolling and clicking Never happens; lazy-load on viewport entry Cookies and Web Storage Cleared across page loads Cache-Control headers May be ignored; fingerprint file names Permission prompts, WebSockets Declined; HTTP requests only Shadow DOM Flattened with light DOM; use <slot> href="#/products" routes Not reliably resolved; use History API Table 10.12: Where Googlebot's renderer differs from a user's browser In single-page apps the server answers 200 for every route, so a "not found" view becomes an indexable soft 404. Redirect to a URL that really returns 404, or inject noindex. The reverse fails: if the initial HTML contains noindex, rendering may be skipped, so a script that removes it never runs.

    const res = await fetch(`/api/products/${id}`);

    if (res.status === 404) {

    document.head.insertAdjacentHTML('beforeend', '<meta name="robots" content="noindex">');

    renderNotFound();

    } else {

    renderProduct(await res.json());

    }

    Listing 10.21: Marking a missing product as noindex in a client-side router Under the hood: WRS may skip requests that add no content, such as analytics beacons, so your analytics undercount Googlebot. The URL Inspection tool (Section 10.3.14) shows the rendered HTML, loaded resources and console messages.

    10.9.3 Structured Data Testing and Rich Results

    Valid JSON-LD (Section 2.11.3) makes a page eligible for a rich result, but Google says it "does not guarantee" one. Templates and serving issues break markup after launch, so test at every stage: the Rich Results Test while developing, the Schema Markup Validator (https://validator.schema.org/) for schema.org rules without Google's, then the URL Inspection tool and Search Console's site-wide rich result reports.The Rich Results Test (https://search.google.com/test/rich-results) accepts a URL or a code snippet and fetches as Google-InspectionTool on a smartphone, so robots.txt can block it and a local server needs a tunnel. It reads the rendered source, so JSON-LD injected by JavaScript counts, though Google warns this makes Shopping crawls "less frequent and less reliable".Catch syntax errors in the build first. Run this script on each built page (node check-jsonld.mjs dist/kettle.html); here the page's second block has a trailing comma:

    import { readFileSync } from 'node:fs';

    const html = readFileSync(process.argv[2], 'utf8');

    const re = /<script type="application\/ld\+json">([\s\S]*?)<\/script>/g;

    for (const [, json] of html.matchAll(re)) {

    try {

    console.log('OK ', JSON.parse(json)['@type']);

    } catch (err) {

    console.log('ERROR', err.message);

    process.exitCode = 1;

    }

    }

    Listing 10.22: check-jsonld.mjs: failing a build on broken JSON-LD

    OK Product

    ERROR Expected double-quoted property name in JSON at position 80 (line 2 column 80)

    Output of Listing 10.22 Warning: The Rich Results Test ignores comments inside JSON-LD, but JSON does not allow them; strip them before publishing. Also check that a feature still exists: How-to results ended in 2023, five types including Course info and Vehicle listing were retired in September 2025, and FAQ rich results stopped appearing on May 7, 2026.

    10.9.4 International and Multi-regional Sites

    A multilingual site offers several languages; a multi-regional site targets several countries. Google recommends a separate URL for each version, annotated with hreflang (Section 10.3.11), and advises against URL parameters such as ?loc=de: Structure Example Strengths Weaknesses Country-code domain example.de Clearest geotargeting Costly; one country each Subdomain de.example.com Easy; separate servers "de": language or country? Subdirectory example.com/de/ Easy; one host Harder to separate Table 10.13: URL structures for international sites Google treats .eu, .asia and vanity ccTLDs such as .tv, .ai and .co as generic domains, ignores geo.position meta tags, and detects language from visible text, not the lang attribute (keep lang for screen readers).Locale-adaptive pages, which switch content by IP address, cookie or Accept-Language, are risky: Googlebot mostly crawls from US addresses, sends no Accept-Language header and uses one user agent everywhere, so it may never see your other variants. Don't redirect visitors by guessed language; suggest the other version instead:

    <!doctype html>

    <html lang="de" style="font-family: system-ui, sans-serif">

    <p id="suggest" hidden style="background: #e3f2fd; padding: 10px">

    This page is also available in English. <a href="/en/kettle">Switch to English</a>

    </p>

    <h1>Wasserkocher aus Edelstahl</h1>

    <script>

    if (navigator.languages.some((lang) => lang.startsWith('en'))) {

    document.getElementById('suggest').hidden = false;

    }

    </script>

    Listing 10.23: Suggesting a language version instead of redirecting Figure 10.6: Browser output of Listing 10.23 Tip: When regional versions share a language and nearly identical content, such as example.de/ and example.com/de/, Google advises picking a preferred version with rel="canonical" plus hreflang. Translate whole pages, not just the navigation, so each URL reads as one language.

    10.10 Generative Engine Optimization

    Generative engine optimization (GEO), also sold as answer engine optimization (AEO), aims to get a site cited in AI answers. Google's own guide says that for its AI features this "is still SEO". Other assistants run their own crawlers and indexes, so a few controls and measurements are new.

    10.10.1 How AI Assistants Choose and Cite Sources

    AI assistants answer current questions with retrieval-augmented generation (RAG), which Google says is "also known as grounding": the model searches an index, reads the retrieved pages and writes an answer with links to the pages that support it. Google grounds AI Overviews and AI Mode in its core ranking systems and query fan-out (Section 10.1.3). ChatGPT search surfaces sites through OAI-SearchBot, Perplexity through PerplexityBot, and Claude through Claude-SearchBot and Claude-User (Section 10.10.2).Figure 10.7: A grounded AI answer and where a page can drop outIn Anthropic's web search API, for example, the model decides when to search, may search several times, and attaches citations to parts of its answer, each with a URL, a title and up to 150 characters of cited_text. A citation points at a passage, so facts you want credited must be text in your initial HTML (Section 10.9.1).What the evidence says. The 2023 paper GEO: Generative Engine Optimization (Aggarwal et al., KDD 2024) tested page rewrites on its GEO-bench query set: adding citations, quotations and statistics raised visibility by 30-40% on its main metric, while keyword stuffing did not help. For its own AI features, Google says to skip chunking content, rewriting for AI, buying mentions and special schema.org markup, and to write "non-commodity content": first-hand experience and data that summaries cannot supply. Warning: Google states: "No third-party tool has access to our internal ranking or AI systems." Treat any list of AI citation ranking factors as a hypothesis, not documentation.

    10.10.2 llms.txt, AI Crawlers and robots.txt Controls

    AI companies separate training crawlers from search crawlers and user-triggered fetchers, each with its own robots.txt token (Section 2.11.4). Blocking OpenAI's or Anthropic's training bot keeps you in their AI search; blocking their search bot can remove you. robots.txt token Operator Purpose GPTBot OpenAI Training foundation models OAI-SearchBot OpenAI ChatGPT search results ChatGPT-User OpenAI Visits a user asked for ClaudeBot Anthropic Training Claude-SearchBot Anthropic Search result quality Claude-User Anthropic Fetches for a user's question PerplexityBot Perplexity Perplexity search; not training Google-Extended Google Gemini training and grounding Applebot-Extended Apple Apple model training Table 10.14: AI crawler and control tokens (September 2026) The two -Extended tokens only control use: Googlebot and Applebot fetch, and blocking them affects neither Google Search nor Apple's Spotlight, Siri and Safari results. User-triggered fetchers such as ChatGPT-User and Perplexity-User may ignore robots.txt, and OpenAI and Perplexity need up to about 24 hours to apply changes.

    User-agent: GPTBot

    User-agent: ClaudeBot

    User-agent: Google-Extended

    User-agent: Applebot-Extended

    Disallow: /

    User-agent: *

    Allow: /

    Listing 10.24: robots.txt: search crawlers allowed, training tokens blocked To leave Google's AI Overviews and AI Mode without leaving Search, use the Search generative AI setting that every Search Console property has had since August 31, 2026, or page-level nosnippet rules (Section 10.1.3).llms.txt, proposed by Jeremy Howard in September 2024 and revised as v2 in August 2026, is a Markdown file at /llms.txt (or /docs/llms.txt) with an H1 site name, a blockquote summary and H2 lists of links, ideally to .md versions of pages, for agents such as coding assistants reading documentation. Mintlify, GitBook, Yoast SEO and Wix generate one, and Lighthouse checks only that fetching it causes no server error. Google Search ignores it, so it will "neither harm nor help" rankings: publish one for developer docs, not for search visibility.

    10.10.3 Measuring Visibility in AI Answers

    Measure impressions and citations, not only clicks. Four sources are dependable:
  • Google Search Console. The Generative AI performance report, open to all sites since August 31, 2026, shows impressions in AI Overviews and AI Mode by page, country, date and device.

  • Bing Webmaster Tools. AI Performance, in public preview since February 2026, counts citations per URL in Microsoft Copilot and Bing's AI summaries, with sample "grounding queries".

  • Server logs. They show which pages each AI crawler fetches; check hits against the IP ranges the operators publish, since user agents are easy to fake.

  • Analytics referrals. Visits from assistant domains such as chatgpt.com, perplexity.ai and copilot.microsoft.com appear as referral traffic when the browser sends a referrer.

  • grep -oE 'GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-User|PerplexityBot' \

    /var/log/nginx/access.log | sort | uniq -c | sort -rn

    Listing 10.25: Counting AI crawler requests per user agent, busiest first For assistants without publisher reports, ask a fixed set of real customer questions each month and record whether your pages are cited. Answers vary between runs, so track trends; commercial trackers automate this without any inside access to the models.Start with the basics that serve every engine: server-render your content, keep crawlers you want unblocked, publish first-hand information others cannot copy, and review these reports every month.

    A. HTML Element Reference (Every Element in One Table)

    All 113 elements of the WHATWG HTML Living Standard, plus the embedded svg and math, in the spec's category order: metadata, sections, grouping, text, edits, embedded, tables, forms, interactive, scripting. § gives the section that teaches each. Obsolete elements such as center, font, frame, marquee and param fail validation. Element Purpose § Element Purpose § html, head, body Document skeleton 2.2 title, meta, link Metadata 2.2.2 style, base CSS; base URL 2.2.2 header, footer Intro; footer 2.2.3 main, nav, aside Landmarks 2.2.3 article, section Content sections 2.2.3 h1-h6, hgroup Headings 2.3.2 address Contact details 2.3.4 search Search landmark 2.6.4 p, hr, div Paragraph; blocks 2.2.3 pre, blockquote Verbatim; quote 2.3.2 ol, ul, li, menu Lists 2.4.1 dl, dt, dd Description list 2.4.1 figure, figcaption Captioned figure 2.4.4 a Hyperlink 2.3.1 b, i, u, s, q Offset text; quote 2.3.2 em, strong, mark Stress; highlight 2.3.4 small, sub, sup Side note; scripts 2.3.2 cite, dfn, abbr Work; term; abbr. 2.3.4 code, kbd, samp Code; keys; output 2.3.4 var, data, time Variable; values 2.3.4 span, br Generic; newline 2.3.2 ruby, rt, rp Ruby annotation 2.3.3 bdi, bdo, wbr Bidi; break hint 2.3.3 ins, del Edits 2.3.2 img, map, area Image; image map 2.4.4 picture, source Art direction 2.4.6 iframe Nested page 2.2.4 audio, video Media players 2.4.5 track Captions 2.4.5 embed, object External content 2.4.5 table, caption Table; title 2.4.3 thead, tbody Row groups 2.4.3 tfoot, tr Footer; row 2.4.3 th, td Header; data cell 2.4.3 col, colgroup Columns 2.4.3 form, label Form; label 2.5.1 input Typed control 2.5.2 button, textarea Button; multiline 2.5.1 select, option Drop-down list 2.5.1 optgroup Option group 2.5.1 selectedcontent Chosen option 2.5.5 datalist Suggestions 2.5.2 output Calculation result 2.5.3 fieldset, legend Control group 2.5.1 progress, meter Task; gauge 2.4.2 details, summary Disclosure 2.6.3 dialog Dialog box 2.6.1 script Scripts 6.1.3 noscript No-script fallback 2.2.2 template, slot Inert DOM; slot 2.2.5 canvas Scripted bitmap 9.11 svg Vector graphics 4.10.9 math MathML 9.5.7 Table A.1: HTML elements in WHATWG category order

    B. CSS Property Quick Reference

    The most-used properties by area (box model, flexbox and grid, text, visual effects, motion), with initial values from the CSS specifications. An asterisk marks an inherited property. Property Initial § Property Initial § display inline 4.8.11 box-sizing content-box 4.8.1 width, height auto 4.8.3 max-width none 4.8.3 margin, padding 0 4.8.4 border-style none 4.8.4 border-radius 0 4.8.5 box-shadow none 4.8.5 position static 4.8.2 inset, z-index auto 4.8.2 overflow visible 4.5.3 aspect-ratio auto 4.8.3 flex 0 1 auto 4.8.6 flex-direction row 4.8.6 justify-content normal 4.8.6 gap normal 4.8.6 grid-template-* none 4.8.7 container-type normal 4.10.12 color* CanvasText 4.2.6 font-family* UA-dependent 4.5.6 font-size* medium 4.5.6 font-weight* normal 4.5.6 line-height* normal 4.5.2 text-align* start 4.5.1 background-color transparent 4.6.1 opacity 1 4.6.3 filter none 4.6.2 visibility* visible 4.6.3 transform none 4.8.8 transition-duration 0s 4.9.1 animation-name none 4.9.2 anchor-name none 4.8.10 Table B.1: Common CSS properties with initial values

    C. JavaScript Built-in Objects Quick Reference

    ECMAScript's global objects with their most-used static and prototype members (from is Array.from(), at is arr.at()). Object Key members § Object Key members § Object keys, hasOwn, is 6.3.7 JSON parse, stringify 6.3.2 Function call, apply, bind 6.5.6 Symbol for, keyFor 6.11.4 Number isInteger, isNaN 6.10.2 BigInt asIntN, asUintN 6.10.5 Math round, sumPrecise 6.17.2 String padStart, split 6.12.4 Array from, toSorted, at 6.13 Iterator from, map, take 6.16.5 Map, WeakMap groupBy, get, set 6.15.2 Set, WeakSet union, difference 6.15.1 Promise all, any, race 6.6.7 RegExp exec, test 6.19.6 Date now, toISOString 6.18 Temporal Now, PlainDate 6.18.4 Intl NumberFormat 6.20 Error cause, isError 6.4.3 Proxy, Reflect get, set, ownKeys 6.21 ArrayBuffer resize, transfer 6.14.1 Uint8Array fromBase64, toHex 6.14.5 Atomics wait, notify 6.14.4 WeakRef deref 6.15.3 globalThis The global object 6.2.4 Table C.1: JavaScript built-in objects and key members

    D. VS Code and Chrome DevTools Keyboard Shortcuts

    Default bindings from the official documentation, VS Code on the left and Chrome DevTools on the right. On Linux, VS Code copies a line with Ctrl+Shift+Alt+Down and formats with Ctrl+Shift+I. Help > Keyboard Shortcut Reference prints VS Code's full list (Section 1.2.3); DevTools tooltips show theirs (Section 1.3). VS Code Win/Linux macOS DevTools Win/Linux macOS Command Palette Ctrl+Shift+P Cmd+Shift+P Open DevTools F12 Cmd+Option+I Quick Open file Ctrl+P Cmd+P Open Console Ctrl+Shift+J Cmd+Option+J Find in files Ctrl+Shift+F Cmd+Shift+F Inspect element Ctrl+Shift+C Cmd+Shift+C Go to line Ctrl+G Ctrl+G Command Menu Ctrl+Shift+P Cmd+Shift+P Go to symbol Ctrl+Shift+O Cmd+Shift+O Open file Ctrl+P Cmd+P Select next match Ctrl+D Cmd+D Device Mode Ctrl+Shift+M Cmd+Shift+M Move line Alt+Up/Down Option+Up/Down Search all files Ctrl+Shift+F Cmd+Option+F Copy line down Shift+Alt+Down Shift+Option+Down Hard reload Ctrl+Shift+R Cmd+Shift+R Toggle comment Ctrl+/ Cmd+/ Pause/resume F8 F8 Format document Shift+Alt+F Shift+Option+F Step over F10 F10 Definition; rename F12; F2 F12; F2 Step into; out F11; Shift+F11 F11; Shift+F11 Toggle terminal Ctrl+` Ctrl+` Toggle breakpoint Ctrl+B Cmd+B Toggle sidebar Ctrl+B Cmd+B Clear Console Ctrl+L Cmd+K Table D.1: Default keyboard shortcuts in VS Code and Chrome DevTools

    E. Glossary

    Numbers in parentheses give the section that explains each term.ARIA: attributes that expose roles and states to assistive technology (2.10). AST: the abstract syntax tree a parser builds from source code (7.1.3). Baseline: a feature that works in Chrome, Edge, Firefox and Safari (1.1.3). BEM: Block-Element-Modifier class naming (4.12.3). bfcache: the back/forward cache that restores pages instantly (9.10). Bundler: a tool that joins modules into optimized files (1.8.1). Cascade layer: an @layer that ranks groups of rules before specificity (4.3.14). Closure: a function plus the variables it captured (6.5.7). Container query: styles chosen by a container's size (4.10.12). Core Web Vitals: LCP, INP and CLS, Google's user-experience metrics (9.10). CORS: headers that let a server share resources across origins (2.8.1). CSP: a policy restricting where scripts and other resources load from (2.8.5). Custom element: an author-defined HTML tag backed by a class (2.7.1). Custom property: a CSS variable written --name (4.2.10).DOM: the live object tree of a document (8.1.1). E-E-A-T: Experience, Expertise, Authoritativeness and Trustworthiness, Google's quality criteria (10.1.2). Event delegation: one listener on an ancestor handling events from many children (8.8.1). Event loop: the scheduler that runs tasks and microtasks (6.6.1). Formatting context: the layout mode, such as block or flex, of a box's children (4.8.11). Generic: a type parameterized by other types (7.7.1). GEO: optimizing content to be cited by AI assistants (10.10). Hoisting: declarations processed before code runs (6.5.3). Hydration: attaching JavaScript to server-rendered HTML (10.9.1). Islands: interactive components in an otherwise static page (1.12.7). JIT: just-in-time compilation of hot code to machine code (6.1.4). JSON-LD: structured data in a script block (2.11.3). Microtask: a queued callback, such as a promise reaction, run before the next task (6.6.5). Narrowing: refining a union type with checks (7.4).Polyfill: code that implements a missing feature (9.13). Preflight: Tailwind's base reset stylesheet (5.4). Progressive web app: an installable, offline-capable site (9.4.13). Prototype chain: the linked objects searched for inherited properties (6.3.10). Reflow: recalculating layout after a change (8.1.4). Secure context: a page served over HTTPS or localhost, required by powerful APIs (9.1). Service worker: a script that intercepts network requests (9.4.4). Shadow DOM: an encapsulated subtree with scoped styles (2.2.5). Signals: reactive values that update dependents directly (8.8.3). Source map: a file mapping built code back to source (6.24.3). Specificity: the weight that decides between competing selectors (4.3.12). SSR and SSG: rendering HTML on the server per request or at build time (1.12.7). Stacking context: a group painted as one layer along the z-axis (4.8.2). Temporal dead zone: the span before a let or const is initialized (6.2.4). Tree shaking: removing unused exports from a bundle (1.8.1). Utility-first: styling with single-purpose classes (5.1.1). Virtual DOM: an in-memory tree diffed to patch the real DOM (8.8.3). XSS: injecting script into a page through unsanitized input (8.8.4).

    F. Index of Open-source Libraries and Tools (GitHub URLs, by Chapter)

    Every open-source project whose GitHub repository this book cites, listed under the chapter that first mentions it and sorted by owner. Prefix each path with https://github.com/ to reach the repository.Chapter 1. angular/angular, angular/angular.js, caddyserver/caddy, certbot/certbot, ChromeDevTools/chrome-devtools-mcp, ChromeDevTools/devtools-frontend, ddclient/ddclient, emberjs/ember.js, FiloSottile/mkcert, GoogleChrome/lighthouse, ionic-team/ionic-framework, karma-runner/karma, lit/lit, nuxt/nuxt, nvm-windows/nvm, preactjs/preact, QwikDev/qwik, react/react, solidjs/solid, sveltejs/svelte, vuejs/core, withastro/astro.Chapter 2. alpinejs/alpine, bigskysoftware/htmx, cheeriojs/cheerio, cure53/DOMPurify, davidjbradshaw/iframe-resizer, dequelabs/axe-core, emmetio/emmet, evanw/esbuild, expressjs/cors, flatpickr/flatpickr, handlebars-lang/handlebars.js, helmetjs/helmet, inikulin/parse5, jsdom/jsdom, keithamus/invokers-polyfill, Leaflet/Leaflet, libjxl/libjxl, lit/lit/tree/main/packages/labs/ssr, lovell/sharp, mde/ejs, mozilla/nunjucks, nvaccess/nvda, orchidjs/tom-select, pa11y/pa11y, parcel-bundler/lightningcss, paulirish/lite-youtube-embed, pugjs/pug, shoelace-style/webawesome, stenciljs/core, TanStack/table, terser/html-minifier-terser, terser/terser, validator/validator, vercel/satori, web-platform-dx/web-features.Chapter 3. bootstrap-vue-next/bootstrap-vue-next, ColorlibHQ/AdminLTE, coreui/coreui, desandro/masonry, FullHuman/purgecss, mdbootstrap/mdb-ui-kit, ng-bootstrap/ng-bootstrap, react-bootstrap/react-bootstrap, tabler/tabler, thomaspark/bootswatch, twbs/bootstrap.Chapter 4. csstools/postcss-plugins, floating-ui/floating-ui, fonttools/fonttools, postcss/autoprefixer, postcss/postcss, svg/svgo, unjs/fontaine.Chapter 5. dcastil/tailwind-merge, htmlstreamofficial/preline, joe-bell/cva, lukeed/clsx, saadeghi/daisyui, shadcn-ui/ui, sindresorhus/modern-normalize, tailwindlabs/headlessui, tailwindlabs/prettier-plugin-tailwindcss, tailwindlabs/tailwindcss-typography, tailwindlabs/tailwindcss/releases/latest, themesberg/flowbite.Chapter 6. axios/axios, colinhacks/zod, date-fns/date-fns, fullcalendar/temporal-polyfill, iamkun/dayjs, immerjs/immer, lodash/lodash, MikeMcl/decimal.js, moment/luxon, promises-aplus/promises-tests, ramda/ramda, ReactiveX/rxjs, sindresorhus/escape-string-regexp, sindresorhus/p-limit, uuidjs/uuid, zloirock/core-js.Chapter 7. AssemblyScript/assemblyscript, DefinitelyTyped/DefinitelyTyped, facebook/flow, fi3ework/vite-plugin-checker, mmkal/expect-type, rolldown/tsdown, sindresorhus/type-fest, typescript-eslint/typescript-eslint.Chapter 8. fabiospampinato/cash, jquery/jquery, jquery/jquery-migrate, TanStack/virtual, wiki/shared/books/front-end/eslint-plugin-no-jquery, wilsonpage/fastdom.Chapter 9. Dash-Industry-Forum/dash.js, dexie/Dexie.js, djipco/webmidi, GoogleChrome/web-vitals, GoogleChrome/workbox, GoogleChromeLabs/comlink, jakearchibald/idb, jakearchibald/idb-keyval, KaTeX/KaTeX, MasterKale/SimpleWebAuthn, mfreed7/xslt_polyfill, Modernizr/Modernizr, motiondivision/motion, openai/whisper, orestbida/cookieconsent, PlasmoHQ/plasmo, plausible/analytics, ronkok/Temml, SortableJS/Sortable, Tonejs/Tone.js, utterance/utterances, Vanilagy/mediabunny, video-dev/hls.js, wxt-dev/wxt.Chapter 10. microformats/microformats-parser.

    G. Answers to "Test Yourself!"

    Answers to the questions in Section 6.27; all outputs come from Node.js 25.
  • 3 3 3, then 0 1 2. The var callbacks share one i, already 3 when the timers fire; let binds a fresh j per iteration (Sections 6.2.4 and 6.5.7).

  • A D F C E B. An async function runs synchronously until its first await; microtasks (C, then E) run before the next macrotask B (Section 6.6.5).

  • 1 undefined, then a TypeError. The arrow takes this from the module scope (undefined), and the detached inc has no this either (Sections 6.5.2 and 6.23).

  • false [ 1, 10, 9 ] object false true. 0.1 has no exact binary form (Section 6.10.6), sort() compares strings, typeof null is a historical bug, and only Object.is finds NaN equal to itself.

  • [ 1, NaN, NaN ] [ 1, 2, 3 ]. map passes the index as a second argument, so parseInt('2', 1) uses an invalid radix (Section 6.13.4).

  • 1 2. Spread makes a shallow copy, so inner is still shared; structuredClone() copies deeply (Section 6.3.9).

  • null 5 d 0 d. Destructuring defaults apply only to undefined; || rejects every falsy value, ?? only null and undefined (Sections 6.13.5 and 6.22.7).

  • {"d":null,"e":"1970-01-01T00:00:00.000Z"}. undefined, functions and symbol keys are skipped, NaN becomes null, and Date has a toJSON() method (Section 6.3.2).

  • 1 20 true. The first next(5) only starts the generator, so 5 is discarded; 10 becomes x (Section 6.5.9).

  • [ 'rejected', 'fulfilled' ], then AggregateError [ 1, 2 ]. Promise.any rejects only when every input rejects (Section 6.6.7).

  • true false function. #x in o checks for the private field without throwing, and a class is a function (Sections 6.7.6 and 6.5.5).

  • Yes, if each read of a yields a new value. == converts objects through Symbol.toPrimitive, valueOf() or toString() (Sections 6.11.3 and 6.3.8); a Proxy (Section 6.21) works too, as does a global getter, which even fools ===. The old with trick is illegal in modules (Section 6.23). Three ways:

  • let i = 0;

    const a = { [Symbol.toPrimitive]: () => ++i };

    console.log(a == 1 && a == 2 && a == 3);

    const b = [1, 2, 3];

    b.join = b.shift; // arrays convert to strings via join()

    console.log(b == 1 && b == 2 && b == 3);

    let n = 0;

    Object.defineProperty(globalThis, 'c', { get: () => ++n }); // globalThis, not window

    console.log(c == 1 && c == 2 && c == 3, c === 4);

    Listing G.1: Three ways to make a == 1 && a == 2 && a == 3 true

    true

    true

    true true

    Output of Listing G.1