# Site Speed Labs — all guides > Full text of every published guide from https://www.sitespeedlabs.com. The index is at https://www.sitespeedlabs.com/llms.txt. --- # Can ChatGPT read your website? The JavaScript test > Most AI crawlers don't run JavaScript, so text your site builds with scripts can be invisible to them. How to check your pages in five minutes, and what fixes it. Published 2026-10-12 · https://www.sitespeedlabs.com/blog/ai-crawlers-javascript When someone asks ChatGPT, Claude or Perplexity about your business, the assistant often goes and reads your website. What it reads is not always what your visitors see. A person's browser downloads your page and then runs its JavaScript, the code that can add text, prices, reviews and menus after the page arrives. Most AI crawlers skip that second step. They read the page exactly as your server sent it, and anything that only appears once scripts have run is not there for them. For a lot of sites this makes no difference, because the words are in the page from the start. For others it means an assistant sees a heading, a cookie banner and not much else. The good news is that you can tell which kind of site you have in a few minutes, without any technical help. ## What a crawler sees, and what your visitors see A crawler is a program that fetches web pages automatically. Search engines use them to build their index, and AI companies use them to collect pages for training, for search results and for answering a user's question in the moment. When a visitor opens your page, their browser goes through more steps than a crawler does. The difference is where text can go missing.
A visitor's browser
1. Receives the HTML from your server
2. Downloads the JavaScript files
3. Runs them, which can add text, prices, reviews
Sees the whole page
Most AI crawlers
1. Receives the HTML from your server
May download a script file, but does not run it
Sees only the words that were in the HTML
The first step is the same for both. Anything your site adds in step 3 exists for your visitors and not for most AI crawlers.
HTML is the document your server sends: the text, the headings, the links, and references to other files like images and scripts. If a sentence is written into that document, every crawler can read it. If a script writes it in later, only a crawler that runs scripts will see it. ## Which AI crawlers run JavaScript The clearest public measurement comes from Vercel, a hosting company, and MERJ, a search consultancy. In December 2024 they published [an analysis of AI crawler traffic across Vercel's network](https://vercel.com/blog/the-rise-of-the-ai-crawler). Their finding was blunt: "none of the major AI crawlers currently render JavaScript." To render a page means to build it fully, scripts included, the way a browser does.
✗ Read the HTML only
GPTBot OAI-SearchBot ChatGPT-User ClaudeBot PerplexityBot Meta-ExternalAgent Bytespider CCBot
✓ Run JavaScript
Googlebot Gemini (via Google) AppleBot
Most of the crawlers behind today's AI assistants read your HTML and nothing else. Source: Vercel and MERJ, December 2024.
Two caveats. First, this was a measurement taken in late 2024, not a promise from the AI companies, and any of them could start rendering pages at any time. We have not seen a public re-test that found a change. Second, Google is the exception that matters. Its own documentation on [how Google Search processes JavaScript](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) describes a rendering step where "a headless Chromium renders the page and executes the JavaScript," and the same study notes that Gemini uses that infrastructure. So a page can be fine in Google and empty in ChatGPT. The crawlers do fetch script files. They just don't run them. In the same study, JavaScript files made up a noticeable share of what the OpenAI and Anthropic crawlers downloaded.
ChatGPT crawler
11.50%
Claude crawler
23.84%
Share of each crawler's fetches that were JavaScript files. Downloading a script is not the same as running it, so text the script would have produced still never reaches them. Source: Vercel and MERJ.
If you want to control which of these crawlers may visit at all, that is a separate setting in your robots.txt file, which we covered in [AI crawlers in robots.txt](/blog/ai-crawlers-robots-txt). Allowing a crawler in does not help if the words are not in the page when it arrives. ## Where text goes missing on ordinary business sites Most small-business sites are built on platforms that write the text into the HTML on the server. WordPress, for example, does this by default. Problems usually come from one section or one add-on rather than the whole site. These are the patterns we look for: - **A page builder or plugin that draws a section with scripts.** Sliders, tabbed sections, "load more" lists and animated counters are common culprits. The rest of the page is fine and the one section with your key message is empty. - **Third-party widgets.** Reviews, booking calendars, price lists and menus are often embedded from another company's service and filled in by a script. A crawler sees a placeholder. - **Content fetched after the page loads.** Some sites load product details, FAQs or locations from a separate data file once the page is open in a browser. - **Sites built as a JavaScript app.** A site made with a framework such as React and not set up to render on the server can send an almost empty HTML document with a single empty container that scripts fill in later. One thing that is not a problem: text inside a closed accordion or a hidden tab, as long as it is written into the HTML. A crawler reads it whether or not anyone clicks to open it. What matters is whether the words exist in the document, not whether they are visible on screen. ## How to check your own pages in five minutes You don't need developer tools for the first step, and none of these steps change anything on your site.
1. View sourceSearch the source for a sentence you can see on the page
2. JavaScript offReload the page in Chrome with scripts disabled and see what is left
3. Free checkFetches the page the way a crawler does and counts the words it can read
Each step is a little more thorough than the one before. If a key sentence fails step 1, you have your answer already.
**1. View the source.** In Chrome, Edge or Firefox on a computer, right-click the page and choose "View page source" (Ctrl+U on Windows). This shows the HTML exactly as your server sent it. Press Ctrl+F (Cmd+F) and search for a sentence that matters: your main headline, a price, the name of a service. If you find it, crawlers can read it. If you can see it on the page but not in the source, a script put it there. **2. Turn JavaScript off and reload.** Chrome's [DevTools documentation](https://developer.chrome.com/docs/devtools/javascript/disable) gives the steps: open DevTools (F12), press Ctrl+Shift+P (Cmd+Shift+P on a Mac), type "javascript", choose "Disable JavaScript" and reload. Whatever is left on screen is roughly what a fetch-and-read crawler gets. Closing DevTools switches scripts back on. **3. Run our [free check](/audit).** Alongside the speed results from Google's PageSpeed tool, it fetches your page once, with no browser and no JavaScript, and reports how many words of readable text came back. If the page is close to empty, it says so, and where it can tell, it names the likely cause, such as an empty container a framework fills in or text sitting in a data block for scripts to render. A PageSpeed score cannot tell you this, because PageSpeed loads the page with scripts running. Check more than the homepage. Your pricing, services and contact pages are the ones an assistant is most likely to quote. ## What fixing it looks like The fix is to get the words into the HTML your server sends. How that happens depends on what is drawing the text with scripts. | What we found | Usual fix | | :-- | :-- | | One section of a WordPress page built by a slider or builder widget | Rebuild that section with standard blocks that output plain text | | Reviews, menus or prices from a third-party widget | Write the key facts on the page as text, and keep the widget for the interactive part | | A site built as a JavaScript app | Switch on server-side or static rendering in the framework | Server-side rendering means your server builds the full HTML for each request. Static rendering means the pages are built ahead of time, which is how this site works. Google's guidance [recommends these approaches](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) and calls the older trick of serving crawlers a separate pre-built copy "a workaround and not a long-term solution." There is a side benefit. Google's documentation notes that server-side or pre-rendering "makes your website faster for users and crawlers." Text that arrives in the HTML can appear on screen before any script has finished, which helps on slow phones as well as with assistants. If your site is on WordPress, our [WordPress speed fix](/wordpress) covers the theme and plugin side of this. If you want the words checked and fixed on every key page, with a written crawler policy and your prices marked up so assistants quote them correctly, that is the [AI-ready setup](/ai-ready). ## Start with one page Pick the page you would most want an assistant to read correctly, usually your pricing or services page, and run it through the [free check](/audit). It takes a minute or two, and the section headed "What an AI assistant sees" tells you how many of your words made it into the HTML. If the number is healthy, you can cross this off. If it is close to zero, you know where to start. # Website speed monitoring: what to watch, and how often > Why Google's own speed data reports a slowdown a week or more late, which free tools to check, and a simple routine that catches problems early. Published 2026-10-08 · https://www.sitespeedlabs.com/blog/website-speed-monitoring Most websites do not get slow on the day they launch. They get slow later, one change at a time: a plugin update, a chat widget, a new tracking tag from the marketing team, a homepage photo uploaded straight off a phone. Each change is small, nobody tests the speed afterwards, and six months on the site takes twice as long to load. Monitoring is how you notice while it is still one change and not twenty. This post covers what to watch, the free tools that show it, why Google's own data reports problems late, and a routine you can keep up with. ## Two kinds of speed data, and why it matters for monitoring Every speed tool gives you one of two kinds of numbers, and they behave very differently over time. **Lab data** comes from a single test: a program loads your page once, on a simulated phone and network, and reports what happened. Google's [PageSpeed Insights](https://pagespeed.web.dev/) runs a lab test called Lighthouse every time you press the button. Google describes lab data as [collected in a controlled environment and useful for debugging](https://developers.google.com/speed/docs/insights/v5/about). **Field data** is collected from real visitors. Google gathers it from Chrome users who have opted in to sharing usage statistics and publishes it as the [Chrome User Experience Report](https://developer.chrome.com/docs/crux/methodology) (CrUX). This is the data behind the Core Web Vitals, the three measurements Google uses in search: how fast the main content appears, how quickly the page reacts to a tap, and how much it jumps around while loading. We explain all three in [what Google actually measures](/blog/what-google-actually-measures). For monitoring, the difference that matters is time. A lab test tells you about this minute. Field data is a rolling summary of the [previous 28 days](https://developers.google.com/speed/docs/insights/v5/about), and the CrUX data feed runs [about two days behind](https://developer.chrome.com/docs/crux/api). ## Why Google's data tells you late Google judges each Core Web Vital at the [75th percentile](https://web.dev/articles/vitals): a page passes if three out of four visits are good. Combine that with a 28-day window and you get a delay that surprises most owners. Suppose a plugin update on the 1st makes every visit to your homepage slow. On the 2nd, one day out of 28 in the window is slow, so the 75th percentile barely moves. The number only flips once slow visits make up more than a quarter of the window. With steady traffic that is about a week, plus the two-day reporting lag. Here is how the window fills up: | Days since the slowdown | Share of the 28-day window that is slow | What field data shows | | :-- | --: | :-- | | 1 | about 4% | Still good | | 4 | about 14% | Still good | | 7 | 25% | On the edge | | 10 | about 36% | Now failing | | 28 | 100% | Fully failing | That is the simple case, where every visit got slower. Real problems are often partial. A script that only slows phones, or only the booking page, or only visitors in one country, may take much longer to show up, or never cross the line at all while still costing you enquiries. Fixes are delayed the same way. Search Console's Core Web Vitals report, which also uses CrUX data, runs a [28-day monitoring session](https://support.google.com/webmasters/answer/9205520) when you tell it you have fixed an issue. A problem that started on the 1st, was noticed on the 10th and fixed on the 12th can stay visible in Google's reports well into the following month. None of this makes field data less important. It is what Google uses, and it reflects your real visitors. It is just the wrong tool for noticing a problem early. ## Why one lab test is not enough either Lab tests are fast, but a single run is noisy. Lighthouse's own documentation lists [seven sources of variability](https://github.com/GoogleChrome/lighthouse/blob/main/docs/variability.md), from network conditions to the test machine's hardware to the page itself changing between loads (an advert, a rotating banner). The same page can score a few points differently twice in a row with nothing changed. The same document's advice is to stop trusting single results: "The median Lighthouse score of 5 runs is twice as stable as 1 run." For an owner, that means two things. Never react to one bad test. And when you compare before and after a change, run each side several times and compare the middle values. ## What to watch You do not need to watch every page or every metric. Four things cover most of what goes wrong on small-business sites. **The pages that earn money.** Usually the homepage, your main service or product page, and whatever page holds the form, booking widget or checkout. Five pages is plenty to start. Field data may not exist for quieter pages, because CrUX only includes pages and sites that are ["sufficiently popular"](https://developer.chrome.com/docs/crux/methodology), and Google does not disclose the threshold. Lab tests work on any page. **The three Core Web Vitals, on phones.** Google's guidance is to measure the [75th percentile, separately for mobile and desktop](https://web.dev/articles/vitals). Phones are where problems show first, so if you only look at one, look at mobile. **Page weight and the number of outside scripts.** These are not ranking signals, but they are early warnings. A page that went from 2 MB to 5 MB, or from four third-party scripts to eleven, has changed even if the score has not moved yet. PageSpeed Insights lists the largest files and third-party code in its diagnostics. **Whether your words are in the page.** If a theme or page-builder update starts loading your text with JavaScript, people may never notice, but search engines and AI assistants that read raw HTML can lose your content. Our [free check](/audit) shows how much of a page is readable without JavaScript, so you can compare it before and after a big update. ## Free tools that cover most of this | Tool | Kind of data | Good for | | :-- | :-- | :-- | | [PageSpeed Insights](https://pagespeed.web.dev/) | Lab, plus field if available | Checking one page after a change | | [Search Console, Core Web Vitals report](https://support.google.com/webmasters/answer/9205520) | Field (CrUX), grouped by similar pages | Seeing which groups of pages Google considers poor | | [CrUX Vis](https://developer.chrome.com/docs/crux/vis) | Field (CrUX), weekly history | Seeing a trend over many weeks, by phone or desktop | | Your hosting or analytics provider | Field, from your own visitors | Faster feedback, if your platform offers it | CrUX Vis deserves a mention because few owners know it exists. PageSpeed Insights only shows the latest 28-day figure. CrUX Vis charts up to [40 weekly data points](https://developer.chrome.com/docs/crux/vis), so you can see the week a metric started to slide and match it against what changed on the site that week. The underlying [history data updates every Monday](https://developer.chrome.com/docs/crux/history-api). The last row covers what developers call real-user monitoring: a small script on your own site that measures every visit and reports it to you, without waiting for Google. Some hosting platforms include it, and Google publishes a free library called [web-vitals](https://github.com/GoogleChrome/web-vitals) that a developer can add. It gives the fastest field signal, but it is something to set up, not something to check. ## Set a budget, so you know what "worse" means A monitor is only useful if you have decided in advance what counts as a problem. web.dev calls this a [performance budget](https://web.dev/articles/performance-budgets-101): "a set of limits imposed on metrics that affect site performance." It describes three kinds: limits on size (for example, how much JavaScript a page may load), limits on timings (how many seconds until the main content appears), and limits on a tool's score. For a small business, we suggest timing budgets on your money pages, starting from Google's "good" thresholds: main content in 2.5 seconds or less, a response to taps in 200 milliseconds or less, and layout shift of 0.1 or less. If a page is already well inside those, set the budget closer to where it is today, so a slow creep shows up before it reaches the line. Score budgets are the easiest to understand and the noisiest to act on, for the reasons above. If you use one, give it room: "the homepage stays above 80 on mobile, judged on the middle of several runs" works better than "the homepage scores 95". ## A routine you can keep up with Here is a routine that takes a few minutes and catches most regressions. **After every change you know about.** New plugin, theme update, new marketing tag, redesigned page: run PageSpeed Insights on the affected page three times and note the middle score and the main content time. Do the same before the change if you can. This is the single most useful habit, because it ties a slowdown to its cause on the day it happens. **Weekly, on Monday.** Look at CrUX Vis for your site on phones. You are looking for a line that has started to bend, not a single bad week. **Monthly.** Open the Core Web Vitals report in Search Console and check whether any group of pages has moved from good to needs improvement or poor. **Watch for changes you did not make.** On WordPress, plugins and themes can be set to [update automatically, and by default those updates run twice a day](https://wordpress.org/documentation/article/plugins-themes-auto-updates/). WordPress emails the site owner after each automatic update. Treat that email as a reminder to run a quick test, because an update you did not see is still a change. ## When it is worth paying someone to watch If your site changes rarely and you are comfortable running the checks above, the free tools are enough. Paid monitoring starts to make sense when several people can change the site (an agency, a marketing team, automatic updates), when the site directly takes bookings or sales, or when nobody will reliably do the Monday check. That is the job our [Speed Care](/monitoring-app) app will do. It reads Google's real-visitor figures for your money pages every day, warns you when a page is heading for Google's line, and works back to the week a slowdown began, so you know which change to look at. It is monitoring only; fixing is a separate job. ## Start with a baseline You cannot spot a slowdown without knowing where you started. Run our [free check](/audit) on your three most important pages today and keep the results. It shows the main speed measurements, the top fixes in plain language, and how much of each page AI assistants can read. Next time something changes on the site, run it again and compare. # AI crawlers in robots.txt: which to allow, which to block > GPTBot, OAI-SearchBot, ClaudeBot and the rest each do a different job. What blocking each one costs you, and three robots.txt policies you can copy. Published 2026-10-08 · https://www.sitespeedlabs.com/blog/ai-crawlers-robots-txt A lot of site owners have one rule in their head about AI: either "block the AI bots" or "let them in". The trouble is that there is no single AI bot. OpenAI, Anthropic, Perplexity, Google and Apple each send several, and each one does a different job. One collects pages to train future models. Another builds the index that an AI search tool cites from. A third fetches a page only because a person just asked about it. Blocking the wrong one can take you out of the answers people see in ChatGPT or Perplexity, while doing nothing about the training you meant to opt out of. This post explains what each crawler is for, what you lose by blocking it, and three robots.txt policies you can copy. ## What robots.txt is, in one paragraph robots.txt is a plain text file at the root of your site (for example `yoursite.com/robots.txt`). It lists crawlers by name, which the file calls a "user agent", and says which parts of the site each may visit. It is a request, not a lock. Well-behaved crawlers read it and obey; nothing stops a badly behaved one. Every company quoted in this post says its own crawlers respect it, with one important exception we cover below. ## The three jobs an AI crawler can do Once you sort the crawlers by job, the decision gets much simpler. **Training.** The crawler collects pages that may be used to train a future model. Nobody is asking about your business when this happens, and the model will not link back to you because of it. Blocking a training crawler is how you say "don't learn from my content". **Search.** The crawler builds an index that an AI search product looks things up in when someone asks a question. This works like Google's own crawler: if you are in the index, the assistant can find you, quote you and link to you. Blocking a search crawler takes you out of those answers. **User-triggered fetching.** Someone pastes your link into a chat, or asks a question and the assistant decides to read your page right then. The company sends a fetcher on that person's behalf. It is closer to a browser than a crawler, and that is why some companies say robots.txt may not apply to it. ## Which crawler does which job These are the names you put in robots.txt, taken from each company's own documentation. | Company | Training | Search | User-triggered | | :-- | :-- | :-- | :-- | | OpenAI | GPTBot | OAI-SearchBot | ChatGPT-User | | Anthropic | ClaudeBot | Claude-SearchBot | Claude-User | | Perplexity | (none listed) | PerplexityBot | Perplexity-User | | Google | Google-Extended (a control, not a crawler) | Googlebot | (not separate) | | Apple | Applebot-Extended (a control, not a crawler) | Applebot | (not separate) | Sources: [OpenAI's crawler page](https://developers.openai.com/api/docs/bots), [Anthropic's crawler article](https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler), [Perplexity's crawler page](https://docs.perplexity.ai/guides/bots), [Google's list of common crawlers](https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers) and [Apple's Applebot page](https://support.apple.com/en-us/119829). A few details from those pages are worth knowing before you change anything. **OpenAI.** Disallowing GPTBot "indicates a site's content should not be used in training generative AI foundation models". OAI-SearchBot is the one that decides whether you appear in ChatGPT's search answers; OpenAI says sites that opt out of it are not shown in those answers, though they can still appear as plain navigational links. For ChatGPT-User, OpenAI says that "because these actions are initiated by a user, robots.txt rules may not apply". OpenAI also says a robots.txt change takes about 24 hours to reach its search systems. **Anthropic.** Restricting ClaudeBot signals that the site's future material should be left out of training. Claude-SearchBot crawls to improve search results. Disabling Claude-User "prevents our system from retrieving your content in response to a user query". Anthropic says its bots honour robots.txt. **Perplexity.** PerplexityBot surfaces and links sites in Perplexity's search results, and Perplexity says it is not used to crawl content for training foundation models. Perplexity-User, its user-triggered fetcher, "generally ignores robots.txt rules" because a person asked for the page. **Google.** Google-Extended is not a separate crawler. Google crawls with its usual crawlers and reads the Google-Extended rule to decide whether the content may be used to train and ground Gemini models. Google states that it "does not impact a site's inclusion in Google Search nor is it used as a ranking signal". It also does not take you out of AI Overviews. Google says pages appear in [AI Overviews and AI Mode](https://developers.google.com/search/docs/appearance/ai-features) on the same terms as ordinary search results: indexed, and allowed to show a snippet. The only controls there are the normal search ones (`nosnippet`, `max-snippet`, `noindex`), and they affect your regular listing as well. **Apple.** Applebot-Extended does not crawl anything. It controls whether pages Applebot already collected for Siri, Spotlight and Safari may train Apple's models. Apple says pages that disallow it "can still be included in search results". ## What blocking each one costs you The pattern from the table: blocking a training crawler costs you very little that you can see today, and blocking a search crawler costs you visibility right away. | If you block | You stop | You keep | | :-- | :-- | :-- | | GPTBot, ClaudeBot, Google-Extended, Applebot-Extended | Use of future crawls for model training (by companies that honour it) | ChatGPT, Claude and Perplexity search, Google Search, AI Overviews | | OAI-SearchBot, Claude-SearchBot, PerplexityBot | Appearing in those assistants' search answers | Google Search | | ChatGPT-User, Claude-User, Perplexity-User | Some on-request reads, where the company honours it | Everything else | | Googlebot | Google Search, including AI Overviews | Almost nothing, for most businesses | Note the wording in the first row. A training opt-out applies to what is collected from now on. It does not reach into models that have already been trained. ## Three policies you can copy Most small businesses want one of these three. Pick one on purpose, then write it down. **1. Open to everything.** You want to be found by every assistant and you don't mind your pages being used for training. If your robots.txt has no rules for these crawlers, you already have this policy. Naming them makes it explicit: ``` User-agent: * Allow: / User-agent: GPTBot Allow: / User-agent: OAI-SearchBot Allow: / ``` **2. Cited, but not trained on.** This is the policy most owners have in mind when they say "block AI". You stay in AI search answers and opt out of training: ``` User-agent: * Allow: / User-agent: GPTBot Disallow: / User-agent: ClaudeBot Disallow: / User-agent: Google-Extended Disallow: / User-agent: Applebot-Extended Disallow: / ``` The search crawlers (OAI-SearchBot, Claude-SearchBot, PerplexityBot) fall under the `*` group and stay allowed. One caution: a crawler only obeys the group that names it. If you write a named group for, say, OAI-SearchBot, it ignores your `*` rules entirely, so any `Disallow` lines you rely on (an admin area, a checkout page) need repeating in that group. **3. Closed to AI entirely.** You list every crawler in the table under `Disallow: /`, except Googlebot and Applebot if you still want Google and Apple search. Be clear about the trade: you disappear from ChatGPT, Claude and Perplexity search, and the user-triggered fetchers may still read a page someone links them to. You stay in Google's AI Overviews unless you also restrict snippets in Google Search. If you want to state your intent in words as well as rules, [Content Signals](https://contentsignals.org) adds lines such as `search=yes, ai-input=yes, ai-train=no` to robots.txt. It is new and voluntary, and we explain how we use it on this site in [what "AI-ready" means for a website](/blog/making-this-site-readable-by-ai-agents). ## How to find out what your site says today Open `yoursite.com/robots.txt` in a browser. Look for `User-agent:` lines naming any of the crawlers above, and for `Disallow: /` under them. A `Disallow: /` under `User-agent: *` blocks every crawler that has no group of its own, AI and search alike. If the file looks fine but AI tools still can't reach you, robots.txt may not be the cause. Three common places a block hides: - **Your CDN or firewall.** Since July 2025 Cloudflare has [asked every new domain at sign-up whether to allow AI crawlers](https://www.cloudflare.com/press-releases/2025/cloudflare-just-changed-how-ai-crawlers-scrape-the-internet-at-large/), with blocking as the default, and it now sorts AI bots by what they do: [search, agent and training](https://developers.cloudflare.com/bots/concepts/bot/). A block there turns crawlers away before they ever read robots.txt, and it does not show up in the file. - **A plugin or theme setting.** SEO and security plugins on WordPress and similar platforms can write robots.txt rules for you. If your file has rules nobody remembers adding, that is usually where they came from. - **A rule copied from a template.** Lists of "AI bots to block" get copied from site to site, and some mix search crawlers in with training ones. Changes are not instant. OpenAI quotes about 24 hours. Google says recrawling can take days or longer. ## What robots.txt cannot do It cannot enforce anything. It is a request that the companies above say they honour. Crawlers that don't identify themselves, or that pretend to be someone else, are a firewall problem, not a robots.txt problem. It cannot undo the past. Opting out of training covers what is collected from now on. It cannot make an assistant read you well. A crawler that is allowed in still needs to find your words in the page. If your text only appears after JavaScript runs in a browser, a crawler that does not run JavaScript sees a nearly empty page. That is a separate question from permission, and a common one. ## Check yours in a minute Our [free check](/audit) reads your robots.txt and tells you which of the main AI crawlers it shuts out of your whole site: GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, PerplexityBot and Google-Extended. It also fetches your page the way a crawler does, with no browser and no JavaScript, and counts the words it can read. If you would rather have it set up for you, the [AI-ready setup](/ai-ready) covers the crawler policy along with the rest. # What 'AI-ready' means for a website: nine dimensions, and where ours lands on each > AI readiness is not one thing. It's nine separate questions, from 'can an agent read this page' to 'can an agent buy from you'. What each one means, what this site has for each, and the ones it deliberately leaves empty. Published 2026-09-07 · https://www.sitespeedlabs.com/blog/making-this-site-readable-by-ai-agents People have started asking a new question about their websites. Not "does it rank", but "can an AI agent use this?" If a customer asks their assistant about a company, can the assistant read the site, quote the prices without getting them wrong, and do something useful with what it finds? The trouble with the question is that it's really nine questions, and most of the advice going round answers two of them and calls it done. Below is what each one means, what this site has for it, and where it is deliberately empty. Some of the list costs nothing if you already build fast pages. Some is a draft standard worth an hour. A good part of it does not apply at all to a business that sells services rather than products, and saying so is more useful than padding the list. ## The nine dimensions The dimensions build on each other. A site that can't be read can't be used, and a site that can't be found won't be read. So they stack into levels, and the level you're aiming at depends on what kind of business you are.
LEVEL 4
can transact
6Authenticated
OAuth metadata, only for gated data
7Transactable
product schema, agentic checkout
LEVEL 3
can be used
4Factual
schema.org, JSON feeds, OpenAPI
5Usable
MCP server, skill file
LEVEL 2
bot-aware
2Discoverable
sitemap, well-known files, API catalog, Link header
3Permitted
crawler groups, Content Signals, sane rate limits
LEVEL 1
can be read
1Readable
Words in the HTML without JavaScript, llms.txt, a markdown copy of each page
LEVEL 0Invisible. The words only exist in JavaScript, so a fetch-and-read agent sees nothing.
8Attributable9VerifiedRun alongside every level: decide your training stance, and check from outside your own network that it all works.
Read it from the bottom up. Each level rests on the one below it. The highlighted levels, 1 to 3, are where a services business should aim; level 4 only matters if you have accounts or a product catalogue.
Most business sites sit at level 0 or 1 without knowing which. ## What an agent does when it arrives It helps to know the order. A fetch-and-read agent, which is most of them, doesn't open a browser. It makes one HTTP request at a time and reads what comes back, in roughly this sequence.
robots.txtMay I?crawler groups, Content Signals, rate limits
/.well-known/*  llms.txtWhat's here?sitemap, API catalog, MCP card, skills index
page HTML  or  .mdThe wordscontent without JavaScript, schema.org facts
/api/*  /api/mcpDo the thingan OpenAPI call or an MCP tool, guided by a skill file
One driving a real browser adds a fifth stop: the page's own WebMCP tools.
Every dimension below is about one of these four stops.
If any stop fails, the agent either guesses or gives up. Most sites fail at the third stop, which is the cheapest one to fix. ## 1. Readable *If an agent fetches a page, does it get the words, or an empty shell?* Agents don't run your JavaScript. A site whose text only appears after a framework has loaded and rendered it, which describes a lot of business sites built in the last ten years, is empty as far as an agent is concerned. The page that's slow for a visitor on a cheap phone is the same page that's blank to an agent, and for the same reason. It is also the one check on this whole list that a performance report will not do for you, which is why our [free check](/audit) now fetches the page without JavaScript and counts the words that survive. A site can score in the nineties and still hand a crawler thirteen words. That makes the foundation of this dimension free for any site that is already built for speed. On this site it was in place before the phrase "agent-ready" existed, as a side effect of the decisions in [how this site scores 100/100](/blog/how-this-site-scores-100-on-pagespeed): | Check | Result | | :-- | --: | | Content present in the raw HTML, without running JavaScript | Yes | | Prices written as text rather than images or scripts | Yes | | External scripts loaded by the homepage | 0 | | Third-party domains contacted | 0 | | AI crawlers blocked in robots.txt | None | **What the site has.** [`/llms.txt`](https://www.sitespeedlabs.com/llms.txt), in the format proposed at [llmstxt.org](https://llmstxt.org): a heading, a paragraph about what the site is, then lists of links with a sentence about each, so a model doesn't have to crawl and guess. [`/llms-full.txt`](https://www.sitespeedlabs.com/llms-full.txt) carries every guide in one file for anything that wants the whole corpus in a single request. Every guide also exists as markdown at its own address, this one at [`/blog/making-this-site-readable-by-ai-agents.md`](https://www.sitespeedlabs.com/blog/making-this-site-readable-by-ai-agents.md), the same words with the navigation and styling stripped off, advertised from the HTML page with a `` tag. **What it does not have: Accept-header negotiation.** The convention where a request for a page's normal URL carrying `Accept: text/markdown` gets markdown back directly. It cannot work here. These pages are static files, which is a large part of why they're fast, and Vercel matches a file before it applies any rewrite rule, so the HTML always wins. The way round it is serving posts from a function instead of a file, which trades away the thing the site is built on for a convenience the alternate link and llms.txt already cover. Readiness checkers flag this. It is a decision, not an oversight. ## 2. Discoverable *Can an agent find the parts of your site it needs, without crawling everything and guessing?* The second stop is a handful of fixed addresses. An agent looks in them for one reason each: where are the pages, where is the API described, is there an MCP server, are there skills. A missing file means guessing. **What the site has.** The old pair first, since they still matter most: a sitemap that lists every real page, and one canonical URL form with at most one redirect. Then the newer ones, each pointing at something that already existed: - An [OpenAPI](https://www.openapis.org) description of the speed-check API at [`/api/openapi.json`](https://www.sitespeedlabs.com/api/openapi.json), and an [RFC 9727](https://www.rfc-editor.org/rfc/rfc9727) catalog at [`/.well-known/api-catalog`](https://www.sitespeedlabs.com/.well-known/api-catalog) that points at it. That second one exists purely to answer "where is this site's API description?", which sounds pointless until an agent is looking for exactly that. - An MCP Server Card at [`/.well-known/mcp/server-card.json`](https://www.sitespeedlabs.com/.well-known/mcp/server-card.json) carrying the endpoint, transport, protocol version and tool names. The format is a draft that has already changed shape once, so this one includes the fields from both versions in circulation. - An Agent Skills index at [`/.well-known/agent-skills/index.json`](https://www.sitespeedlabs.com/.well-known/agent-skills/index.json), listing the skill file with a SHA-256 digest computed from the real file at build time. A hand-typed hash goes wrong the first time someone edits the file and then sits there, wrong, for months. - An Agentic Resource Discovery manifest at [`/.well-known/ard.json`](https://www.sitespeedlabs.com/.well-known/ard.json), the same map as typed entries with a few example questions each resource answers. Also a draft. All four come from one builder in the codebase, so they cannot disagree with each other or with the routes they describe. **What it does not have: DNS-based discovery.** Publishing agent endpoints as SVCB records under a `_agents` subdomain. Vercel DNS cannot hold that record type, so this one is not a choice. Moving DNS elsewhere for it alone would not be worth the risk for a proposal this young. ## 3. Permitted *Have you told agents what they may do, or are they guessing, or blocked by accident?* [`robots.txt`](https://www.sitespeedlabs.com/robots.txt) is where a lot of sites are accidentally unfriendly. Either a plugin blocked the AI crawlers years ago and nobody noticed, or the file says nothing and everyone is left to guess. **What the site has.** [Content Signals](https://contentsignals.org), a small addition to robots.txt that states what is permitted for each use: ``` User-agent: * Content-Signal: search=yes, ai-input=yes Allow: / ``` `search=yes` means indexing. `ai-input=yes` means an assistant may read the page to answer someone's question. `ai-train` is left unstated on purpose, and that decision gets its own dimension below. Every major AI crawler also has a named group, so anyone looking for the policy finds it written down rather than implied. The API carries a rate limit of fifteen requests a minute per address, which an assistant running one check for one user will never reach. Content Signals is months old, and whether every crawler respects it is not something anyone outside those companies can verify. It is here because writing your policy down costs nothing, not because it can be shown to change anything yet. **What it does not have yet.** The rate limit answers with a 403 when tripped, where a 429 with a `Retry-After` header would be correct. A 403 tells an agent it is banned; a 429 tells it to come back shortly. That one is a genuine gap rather than a decision. ## 4. Factual *When an assistant quotes your prices, will it get them right?* Ask an agent what something costs and it reads a pricing page built for human eyes, then works out which number goes with which tier. That goes wrong quietly. **What the site has.** The tiers as data at [`/api/pricing.json`](https://www.sitespeedlabs.com/api/pricing.json), and [schema.org Offer](https://schema.org/Offer) markup on the pricing page. Both are generated from the single config file the page itself reads, so a price changes in one place and moves everywhere. The three kinds of price are marked as what they are, because a "from" price quoted back to a customer as though it were the price is a complaint waiting to happen. ## 5. Usable *Can an agent do something on your site on a user's behalf, not just read about it?* Everything above lets an agent read the site. This lets it use the site, and for a business with a free tool it is where the leverage is. **What the site has.** An [MCP](https://modelcontextprotocol.io) server at `/api/mcp` exposing three tools. `check_site_speed` takes a URL and returns what a person gets from the [free check](/audit): the score, the three Core Web Vitals labelled as real-visitor or simulated data, the top fixes and roughly what each would save. `get_pricing` covers the moment the conversation turns to cost. `email_full_report` sends the full list to an address the user provides. It runs the same code as the web page, needs no login, holds no session, and sits behind the same rate limit as the rest of the API. Alongside it, an [Agent Skills](https://agentskills.io) file at [`/skills/site-speed-check/SKILL.md`](https://www.sitespeedlabs.com/skills/site-speed-check/SKILL.md). Agent Skills is a format for instructions an agent loads when it needs them. This one covers how to read a result rather than how to fetch one: what lab data means, why a score of 19 can still be a site that loads quickly, and why the per-fix savings must not be added together. Handing an agent a tool is easy. Giving it enough context to explain the answer without misleading someone is the part that takes thought. An agent finds both through dimension 2. The server card, the skills index and llms.txt all carry the addresses. Worth knowing, though: most assistants today still only connect to MCP servers a person has added by hand, so the endpoint belongs somewhere a human can copy it too. **What it does not have: WebMCP.** WebMCP is a browser-side proposal that lets a page announce its functions to an agent driving a browser rather than fetching files. This site used to register its check that way on the audit page. It no longer does: the redesign removed every script we wrote, and WebMCP only exists inside the browser. The MCP server gives an assistant the same tool without any of it. Checkers will flag the gap, and it is a decision. ## 6. Authenticated *If some of your data belongs to a logged-in user, can their agent log in as them?* An agent meeting a tool that needs a login looks for OAuth metadata at fixed addresses: where tokens come from, which issuers and scopes the API accepts, and how a client registers itself without a human creating credentials first. Each step is a published standard. Without the metadata an agent cannot guess, and the user is left pasting an API key by hand, which most will not do. **What the site has: nothing, deliberately.** Three separate readiness checks live here, and passing all three is an afternoon of work: an authorization server document, a protected-resource document, and an `auth.md`. Every tool on this site is free and public. Publishing metadata describing a login that does not exist would send agents off to fetch a token they do not need and imply the API is gated when it is open. Three checks gained, and a worse experience for the very agents the score claims to measure. **What would change that.** Per-customer monitoring data, or account pages. The moment something here belongs to one customer rather than to everyone, these documents stop being decoration and become the only way an agent can reach it on that customer's behalf. Until then the correct number of OAuth files on this site is zero. ## 7. Transactable *Can an agent buy from you without a human filling in the form?* Agentic checkout means an assistant assembles a cart, shows it to the user, takes consent and completes payment through a protocol the merchant supports. Several such protocols exist, each backed by a different large company, and none has won. **What the site has: nothing, and it is not a gap.** Selling an audit or a fix sprint involves a conversation and a scope. There is no cart to assemble. An agent's useful job here is delivering someone to the contact form already knowing what their site scores and what the work costs, which is dimensions 4 and 5, not this one. **If you do sell products**, the practical route is whatever your commerce platform or payment provider turns on, since they absorb the protocol churn. The part worth doing yourself is the layer underneath: complete Product and Offer structured data with price, currency and availability, and a checkout that does not live entirely inside a third-party iframe, because a browser-driving agent fills forms and cannot see into one. ## 8. Attributable *When a model learns from you or cites you, is it on your terms?* Two different things get lumped together as "training". One is a crawler collecting pages to train a future model, months before anyone asks a question. The other is an assistant reading the page right now to answer a user, and citing it. They are separate switches and deserve separate answers. **What the site has.** A yes to the second, through `ai-input=yes`. Silence on the first. The Content Signals spec treats an unstated signal as neither permission nor refusal, which leaves the decision open rather than letting a default make it. The citation side is ordinary hygiene: every post carries a publication date and an organisation as its author, in the page and in the structured data, and URLs do not move. Assistants that cite prefer pages that look like that. **What it does not have.** A TDM reservation, the machine-readable opt-out the EU copyright directive recognises. That form only carries meaning as a refusal, and there is no refusal here to express yet. ## 9. Verified *Do you know, rather than believe, that all of the above works?* Discovery files return 404 from a stale cache edge. A firewall rule blocks the MCP endpoint. A digest goes stale after an edit. None of that shows up in a browser. **Where the site stands.** [isitagentready.com](https://isitagentready.com), a scanner Cloudflare built, tests most of the standards above and scores this site 60 out of 100. Without the server card, skills index and ARD manifest from dimension 2, the same scan returns 40. | Category | Result | | :-- | --: | | Discoverability | 3 / 4 | | Content accessibility | 0 / 1 | | Bot access control | 2 / 2 | | API, auth, MCP & skill discovery | 4 / 8 | | Commerce | not applicable | The six it still wants are the six named above: markdown negotiation, DNS-based discovery, the three OAuth documents, and WebMCP, which needs browser script this site no longer ships. Publishing OAuth files for tools that need no login would lift the number by three in an afternoon and mean nothing. A score is a checklist, and explaining what is missing beats ticking boxes to hide it. It is the same thing we say about PageSpeed scores. **What the scanner does not cover.** Every discovery file gets fetched from outside the network after a deploy, with a cache-busting parameter, because one of them once returned 404 from a stale edge while existing perfectly well at origin. The MCP server gets called through the public URL rather than locally, because rate limits and bot protection block that kind of traffic silently. ## A caveat All of this is new. llms.txt is a proposal, and Google has said its search doesn't use it. Content Signals is months old. MCP changes every few months. Nobody outside the AI companies knows exactly what each crawler and assistant respects today, and anyone promising you'll "show up in ChatGPT" is guessing. It is still worth doing, because all of it can be undone, and the parts that matter most (text in the HTML, no dependence on JavaScript, a written crawler policy, facts stored as data) were good practice long before anyone used the word "agent". ## What to check on your own site Three things, in this order. First, load your page with JavaScript switched off. If the words disappear, an agent sees an empty page, and your visitors on slow phones are waiting a long time to see anything. It's one fix for both problems. Second, read your robots.txt. A surprising number of sites block AI crawlers because a template did it years ago. Decide on purpose. Third, put your prices and key facts in plain text, not in an image or a widget. If a person can copy it, an agent can read it. You do not have to do the first one by hand. The [free check](/audit) on this site fetches your page with no browser and no JavaScript, the way a crawler does, and reports how many words were actually readable in what came back. A PageSpeed score cannot tell you that, because Lighthouse renders the page with scripts running. The same answer comes back through our MCP server, so your assistant can ask for it too. # What Google actually measures when it measures your site speed > Core Web Vitals explained in plain language: what LCP, INP and CLS really mean, the exact thresholds Google uses, and why a site can score 19 out of 100 and still load fine for real visitors. Published 2026-08-14 · https://www.sitespeedlabs.com/blog/what-google-actually-measures If you have ever run your site through Google's PageSpeed tool, you got a number out of 100 and a wall of red. Somewhere underneath were three acronyms — LCP, INP, CLS — and a lot of language that assumed you already knew what they meant. Here is the part nobody leads with: **that score out of 100 is not what Google ranks you on.** The three acronyms are. They measure different things, they can disagree completely, and only one of them is used for search. This post explains all of it in plain language, using real measurements from real sites. ## The three things Google measures Google calls them Core Web Vitals. Each one measures a different kind of frustration. ### Loading — "Largest Contentful Paint" **How long until the main thing on the page shows up.** Not the first pixel, not when the loading spinner stops: the moment the biggest piece of content — usually your hero image or headline — is actually on screen. | | | | :-- | --: | | Good | 2.5 seconds or less | | Needs work | 2.5 – 4 seconds | | Poor | over 4 seconds | ### Responsiveness — "Interaction to Next Paint" **How long the page takes to react when someone taps something.** They tap a button; how long before anything visibly happens? This is the metric behind that feeling of a page being "laggy" — you tap, nothing moves, you tap again, and then both taps register. | | | | :-- | --: | | Good | 200 milliseconds or less | | Needs work | 200 – 500 ms | | Poor | over 500 ms | If you have read about this before and remember something called FID, that was the previous version. INP replaced it as a Core Web Vital in 2024, and it is stricter — FID only measured the delay before the *first* interaction, while INP watches the whole visit. ### Stability — "Cumulative Layout Shift" **How much the page moves around while it loads.** You go to tap a link, an image finishes loading above it, everything jumps down, and you tap an advert instead. That is layout shift, and it is measured as a score rather than a time. | | | | :-- | --: | | Good | 0.1 or less | | Needs work | 0.1 – 0.25 | | Poor | over 0.25 | ## All three, at a glance
Core Web Vitals thresholds Loading is good at 2.5 seconds or less, needs work between 2.5 and 4 seconds, and poor above 4 seconds. Responsiveness is good at 200 milliseconds or less, needs work between 200 and 500 milliseconds, and poor above 500 milliseconds. Stability is good at 0.1 or less, needs work between 0.1 and 0.25, and poor above 0.25. Loading Largest Contentful Paint Good · ≤2.5s Needs work · 2.5–4s Poor · >4s Responsiveness Interaction to Next Paint Good · ≤200ms Needs work · 200–500ms Poor · >500ms Stability Cumulative Layout Shift Good · ≤0.1 Needs work · 0.1–0.25 Poor · >0.25
Pass all three at the 75th percentile and Google considers the page good. Fail one and you fail.
## The bit that trips everyone up: 75th percentile Google does not use your average. It uses the **75th percentile** of real page loads — meaning three out of four visits must hit the target. This matters more than it sounds. Averages hide your worst experiences: if most visitors are on fast laptops on office wifi and a quarter are on mid-range phones on mobile data, the average looks fine and a quarter of your customers are having a bad time. Google deliberately measures so that you cannot average your way out of it. It is also assessed separately for mobile and desktop. Passing on desktop and failing on mobile is a fail where it counts, because most people are on phones. ## Lab data and field data are not the same thing This is the single biggest source of confusion, and it explains almost every "but my score says…" argument. **Lab data** is a simulation. Google loads your page on a pretend mid-range phone over a pretend slow connection, in a data centre. It is repeatable, available for any URL instantly, and it is what produces that score out of 100. **Field data** is real. It comes from actual Chrome users visiting your site over the past 28 days, on their real devices and real connections. This is what Google uses for search ranking. Two consequences people find surprising: **A new or quiet site has no field data at all.** Google needs enough traffic before it will report on you. Below that threshold you only get the simulation — which tells you how the page is *built*, not what anyone experiences. **The two can disagree wildly.** Which brings us to the example. ## A real site that scores 19 and loads fine Here is a car dealership's About page, measured on mobile. Its Lighthouse score is **19 out of 100** — the sort of number that makes people panic. Now the field data, from real visitors: | Metric | Real visitors | Verdict | | :-- | :-- | --: | | Loading (LCP) | 1.7 s | **Good** | | Responsiveness (INP) | 121 ms | **Good** | | Stability (CLS) | 0.40 | **Poor** | Read that again. A site scoring 19 out of 100 is loading its main content in 1.7 seconds and responding to taps in 121 milliseconds. For real people, on real phones, it is **not slow**. It has exactly one problem: it jumps around. A CLS of 0.40 is four times the threshold. Visitors go to tap something and the page moves under their finger. The panic-inducing score of 19 would have you rebuilding the site. The field data says: reserve space for your images and you are largely done. For contrast, here is Wikipedia's homepage on the same day: | Metric | Real visitors | Verdict | | :-- | :-- | --: | | Loading (LCP) | 1.2 s | **Good** | | Responsiveness (INP) | 96 ms | **Good** | | Stability (CLS) | 0.00 | **Good** | Lighthouse score: 100. When a site is genuinely well built, the score and the field data agree. It is when they disagree that you learn something. ## So what should you actually do? **Look at your field data first, not your score.** Google Search Console has a Core Web Vitals report showing exactly what real visitors experience, free, for any site you own. If you only ever check one thing, check that. **If you have no field data, you do not have a traffic problem to solve yet.** Use the lab score as a build-quality check and get on with the rest of your business. **Fix the metric that is actually failing.** Our dealership does not need a faster server — its loading is fine. It needs images with dimensions set. Three metrics, three completely different sets of fixes, and doing the wrong one costs money and changes nothing. **Treat the score out of 100 as a smoke alarm, not a diagnosis.** It is useful for noticing that something is worth investigating. It is not what Google ranks you on, and as the dealership shows, it can be dramatically more alarming than reality. ## The short version - Three metrics: **loading**, **responsiveness**, **stability** - Thresholds: **2.5 s**, **200 ms**, **0.1** - Measured at the **75th percentile** — three in four visits must pass - **Real-visitor data is what counts for ranking**, not the simulated score - A bad score does not always mean a slow site, and a fast site can still fail If you want to know which of the three is your problem, the [free check](/audit) on this site will tell you in plain language — and it will tell you whether Google has real-visitor data for you yet, which is the first thing worth knowing. # How this site scores 100/100 on PageSpeed: every decision explained > A line-by-line account of what makes sitespeedlabs.com load instantly: one self-hosted font, no framework runtime, inlined CSS, and none of our own JavaScript on any page. Including what we gave up to get there. Published 2026-07-30 · https://www.sitespeedlabs.com/blog/how-this-site-scores-100-on-pagespeed There's a badge at the top of this site claiming a perfect 100/100 on Google PageSpeed. Claims like that are cheap, so this post is the receipts: every decision behind the number, why we made it, and what it cost us. We'd rather you finish this and think *"oh, that's all it is"* than *"wow, impressive."* Fast sites aren't clever. They're mostly a series of things you decided not to do. ## What the number actually is Measured on the day we wrote this, on both mobile and desktop: | Measurement | Result | | :-- | --: | | Performance score | **100** / 100 | | Largest Contentful Paint | 0.9 s | | Total Blocking Time | 0 ms | | Cumulative Layout Shift | 0 | | Opportunities flagged by Lighthouse | 0 | One important caveat before we go further, because it's the kind of thing most people posting these screenshots leave out: **this is lab data.** Google simulates a mid-range phone on a throttled connection. It isn't the same as what real visitors experience, which is called field data and comes from Chrome's real-user reporting. This site doesn't get enough traffic yet for Google to publish field data for it — so a perfect lab score is a statement about how the page is *built*, not proof of what every visitor feels. We'd rather tell you that than let you assume otherwise. ## Decision 1: one font, and only one *Updated 8 October 2026.* When this post first went up, the answer here was "no web fonts at all". The site has since been redesigned, and it now uses exactly one. Here is how, and why it didn't cost the score. A typical web font setup costs two or three round trips before any text is readable: fetch a stylesheet from Google, parse it, discover the font file, fetch that from another server, *then* render. Visitors stare at blank space or a flash of the wrong typeface while it happens. That is the cost we refused before, and still refuse. What changed is that one file can now do the work of a family. Archivo is a variable font with two axes, weight and width, so the wide headlines, the narrow labels and the body text you're reading all come from a single 90 KB file. It is served from our own domain, so there is no request to anyone else. It is preloaded, so the browser starts fetching it with the page. And it is set to `font-display: optional`, which means that if it hasn't arrived within about a tenth of a second, the page simply keeps the system font for that visit instead of waiting or reflowing. It can never delay text, and it can never make the layout jump. **What we gave up:** 90 KB, once, cached afterwards. And the bragging right of "no web fonts", which the old version of this post had. We decided a distinctive face was worth one well-behaved file. ## Decision 2: none of our own JavaScript This is the one that moves the needle most on a real site, and the one people underestimate. This site is built with Astro, which renders components to plain HTML at build time and ships no JavaScript to the browser unless you explicitly ask for it. Since the redesign we don't ask for it anywhere. The mobile menu is a native `
` element. The FAQ answers open the same way. The blog's topic filter is radio buttons and CSS. The contact and waiting-list forms are ordinary HTML forms that post to the server and get a confirmation page back. Even the free speed check works with JavaScript switched off. It used to be the one page with a script, 9.7 KB of it, to submit the address and draw the results. Now the form submits to a page that the server renders and *streams*: the "checking" panel arrives instantly, and the results follow in the same response a minute later, when Google has answered. That's why Total Blocking Time is 0 ms. There's nothing to block on. JavaScript is uniquely expensive compared to everything else a page loads. An image of the same size downloads and decodes; a script has to be downloaded, parsed, compiled and executed, all on the main thread, the same single thread the browser needs to respond to a tap. A 300 KB image and 300 KB of JavaScript are not remotely the same cost. This is why phones suffer so much more than the laptop you built the site on. **Being straight about what does load:** two small scripts, for Vercel Analytics and Speed Insights, together about 4.6 KB. They're served from this domain rather than a third party and set no cookies, but they are JavaScript and we're not going to pretend otherwise. "Zero JavaScript" would be a lie. "None of our own JavaScript, on any page" is the truth. **What we gave up:** a little polish. Without a script, a form can't show "Sending..." in place; it goes to a confirmation page instead. A results page can't redraw itself; it streams in from the server. We think that's the right trade for a site whose whole argument is that most of what pages ship is unnecessary. ## Decision 3: the CSS ships inside the HTML One line of config: ```js build: { inlineStylesheets: "always", } ``` Every stylesheet is inlined into the HTML document rather than served as a separate file. The build output contains **zero external CSS files.** This matters because CSS is *render-blocking* — the browser refuses to paint until it has the stylesheet, since painting first would mean flashing unstyled content. An external stylesheet is therefore a mandatory round trip inserted between "HTML arrived" and "anything visible." Inlining removes that round trip entirely. The HTML is bigger, but it arrives in one response, and the browser can paint the moment it finishes reading. **What we gave up:** cross-page CSS caching. A returning visitor re-downloads the styles with each page instead of reusing a cached file. For a site this small, one fewer round trip on first paint beats caching on the second page. On a large app with heavy shared CSS, we'd probably decide the other way. ## Decision 4: icons are inline SVG Every icon is an SVG element written directly into the markup. No icon font, no sprite sheet, no image requests. Icon fonts are a particularly bad deal: you download a whole alphabet to use six glyphs, it's render-blocking like any font, and it renders as tofu boxes if it fails. Inline SVG costs a few hundred bytes inside HTML you were already downloading. It does make the source files noisier — a single icon can be fifteen lines of path data. We'll take verbose source over an extra network request. ## Decision 5: it's just files Every page here is pre-rendered to static HTML at build time and served from a CDN edge near you. There's no server rendering a template per request, no database query, no cold start. The two API endpoints behind the forms are the only server-side code, and nobody hits them just by reading a page. The fastest possible response to "give me this page" is a file that already exists. ## Decision 6: one URL, no redirect hops `sitespeedlabs.com` permanently redirects to `www.sitespeedlabs.com`, and the site declares a single canonical form for every URL — no trailing slashes, one spelling, everywhere. This sounds like housekeeping rather than performance, until you measure it. A redirect is a full round trip before the real page even begins loading. While setting this up, we audited both addresses: the redirecting one scored **99** where the canonical scored 100 — Lighthouse flagged "server is slow to respond" and "requests are chained," and both were really just *the redirect*. One point, from one hop, on a page that was otherwise perfect. If your site sends visitors through two or three redirects — plenty do, especially after a domain migration — that's the cheapest fix you'll ever make. ## Decision 7: nothing third-party Every decision so far was about code we wrote. This one is about code we didn't let in, and for most real sites it matters more than all the others combined. There is no chat widget on this site. No tag manager, no ad pixel, no heatmap recorder, no cookie-consent platform, no A/B testing tool, no "customer engagement" suite. Load the homepage and your browser talks to exactly one domain: ours. The only outbound link on the page is to our X profile, and that's an anchor you can click, not a resource that loads. That matters because third-party scripts are the worst kind of JavaScript. You didn't write them, you can't optimise them, they change without telling you, and they execute on your visitors' devices with the same access to the main thread as your own code. A chat widget you added once and forgot about can quietly triple in size over a year. Your site gets slower and nothing in your own codebase changed. They're also the hardest to remove, because each one is somebody's tool. Marketing wants the pixel, sales wants the chat, someone wants the session recorder. That's a real business conversation, not a technical one. **What we gave up:** genuinely useful things. No live chat means a visitor with a question has to use the contact form. That's a real cost, and for a business doing volume it might be the wrong call. If your site is slow and you only do one thing, open the list of scripts loading on your homepage and ask, for each one, what it earned last quarter. Most sites are carrying at least one they'd struggle to justify. ## What we'd tell you to do first If you took one thing from this: **most of it was subtraction.** One font, no framework, no stylesheet request, no icon library, no redirect chain, nothing third-party. We didn't optimise a slow site into a fast one — we declined to add the things that make sites slow. And if you took one *action* from it, make it the JavaScript. Fonts and images are worth fixing, but scripts are the only thing on the list that competes with your visitors for the thread the browser needs to stay responsive. That's also the honest reason a perfect score is easier for us than for you. This is a marketing site with no logged-in state and no product catalogue, and we can simply decline the tools a bigger business genuinely needs. If you're running one, some of those are non-negotiable, and a 100 may not be available to you. Which is fine. **100 is not the goal.** The goal is Core Web Vitals in the green for your real visitors — that's what Google actually uses for ranking, and it's achievable on almost any site, including ones with a chat widget and a tag manager. A perfect lab score is a nice flex. Green field data is the thing that makes you money. If you want to know where your site stands, the free check on this site will tell you in plain language — and it'll tell you whether Google has real-visitor data for you, which matters more than the score it prints.