Mobile / JS
Viewport, JavaScript rendering and the gap between the HTML you serve and the page a crawler finally sees.
Without JavaScript the page is empty
Almost none of the text is in the server response, so any fetcher that does not run JavaScript reads a blank page: serve the main content in the HTML.
The server sends an almost empty page, and the content appears only once the browser runs the scripts. A person sees no difference. A crawler sees an empty box: a title, a few tags and a place where something will be in a moment. Google can run scripts, but it does so later, in a separate queue and not for every page, so part of your content waits weeks for the second pass, and part never gets it. The crawlers of Bing and of answer engines mostly do not run scripts at all. This is typical of applications built in React, Vue or Angular without server-side rendering. The fixes have names: SSR, prerendering, static generation. Each comes down to the same thing: the content has to be in the HTML the server sends.
- Id
- RAW_HTML_NEARLY_EMPTY
- Fires at
- < 0.3 %
- Fix estimate
- 45 min per page
A script or stylesheet is answered with an HTML page
The server returns an HTML page (often a 404 page or the app shell) where the browser expected code; fix the path or the fallback rule so the file itself is served.
The page asks for a script or a stylesheet, and the server answers with an HTML page. Instead of code the browser gets an error page or the homepage, cannot execute it and silently skips it. The result: no styles, scripts that do not work, sometimes a blank page, and an error in the console about an unexpected character. Usually it is a rule redirecting every unknown address to the homepage, and a file no longer at its old name. Fix the path to the file, or exempt the assets directory from the redirect.
- Id
- ASSET_SERVED_AS_HTML
- Fix estimate
- 30 min per page
A script the page needs never arrives
Check the script path and the deploy: a chunk that 404s usually belongs to a previous build, or the file was never uploaded.
The page asks for a script file the server does not have. Usually it is a piece of the application named with a random string that changed name at the latest deploy, while the old page, kept in a cache or on the CDN, still asks for the previous one. For the reader the page half works: a button does not respond, a section does not load, a form does not send. For a crawler the page may be empty. Purge the cache after a deploy, and keep previous versions of the files for a while so open tabs are not left stranded.
- Id
- BROKEN_SCRIPT_CHUNK
- Fix estimate
- 30 min per page
The script throws away the content the server sent
The served HTML carried the article and the rendered page has lost most of it: a hydration that renders a shell, or a script that clears the page when a request fails; the rendered version is the one indexed.
The server sends the full content and the script deletes it. The reverse of the usual problem: the HTML is good, but after the scripts run part of the text vanishes, because the application redraws the page from scratch and loses what was there. A Google that runs the scripts sees less than one that does not, and there is no telling which version it judges. Usually it is a bug in the application, a component rendering its empty state before the data arrives, or a script that crashed halfway. Start with the browser console on this page.
- Id
- CONTENT_LOST_AFTER_RENDER
- Fix estimate
- 45 min per page
Content only appears after JS renders
The page the server sends and the page after the scripts run are two different pages. Part of the content, headings or links exists in only one of them. Google reads the first one first and the second one later and not always, so for part of the time it judges a page the reader never sees. If the difference concerns the main content, prices or navigation, it matters. If only the cart counter and recommendations, it can stay. Check what is missing in the version without scripts, and move that into the HTML from the server.
- Id
- JS_RENDERING_CHANGED_CONTENT
- Fix estimate
- 30 min per page
The page calls its own API and gets a server error
Open the failing endpoint in the server log; the page shows visitors whatever it renders without that answer.
- Id
- API_ERROR_ON_PAGE
- Fix estimate
- 30 min per page
A script or stylesheet is served with a type the browser refuses
Serve stylesheets as text/css and scripts as text/javascript; the browser refuses the file otherwise and the page runs without it.
- Id
- ASSET_WRONG_MIME_TYPE
- Fix estimate
- 20 min per page
A cross-origin request is refused for want of CORS headers
Send Access-Control-Allow-Origin from the server that hosts the resource, or load it from the same origin as the page.
- Id
- CORS_BLOCKS_RESOURCE
- Fix estimate
- 30 min per page
H1 present only after JS renders
- Id
- H1_ONLY_IN_RENDERED_HTML
- Fix estimate
- 20 min per page
The framework reports server and client HTML disagreeing
The console reports a hydration mismatch: the server rendered one thing and the client another (a date, a random id, browser-only state), so the framework re-renders from scratch; make the first render the same on both sides.
- Id
- HYDRATION_MISMATCH
- Fix estimate
- 30 min per page
Links appear only after scripts run
Put the navigation in the served HTML as plain links, so the pages behind it are found without waiting on JavaScript.
The links on this page come into being only after the scripts run. In the HTML from the server the menu, the product list or the pagination do not exist, so a crawler that does not run scripts has nowhere to go. Google will see them on the second pass, but the second pass comes later and not always, and every page behind such links is discovered late. This is typical of menus built by the application and lists loaded from an API. Links should be in the HTML as ordinary a tags with an address in href.
- Id
- LINKS_ONLY_IN_RENDERED_HTML
- Fix estimate
- 25 min per page
Missing mobile viewport tag
The page does not tell a phone how to display it, so the phone shows a shrunken desktop version: tiny text, a menu a finger cannot hit. One line in the head, the viewport tag, fixes it on every page built from this template. Google judges pages mobile-first, and a missing viewport reads as "this page is not made for phones", while most traffic is phones. Usually it is an old template or a page built by hand years ago.
- Id
- MISSING_VIEWPORT
- Fix estimate
- 5 min per page
Two builds of the site served at once
One reading saw the same bundle under two hashes on two sets of pages: a cache holding the previous release next to the new one. Purge the page cache after the deploy, or publish the assets and the HTML in one step.
The site serves two versions of itself at once: some responses come from the new deploy, some from the old. Usually it is several servers behind a load balancer, one of which missed the update, or a CDN holding old files next to new. The reader sees the new page one moment and the old the next; the application asks for files the other version does not have and breaks. Google sees an unstable site. Check that every server runs the same version, and purge the CDN cache after a deploy.
- Id
- MIXED_BUILD_COHORTS
- Fix estimate
- 60 min per page
The page is wider than a phone screen
At 390px the document overflows sideways, usually a table, a wide image or a block with a fixed width; let it wrap or scroll inside its own box so the page itself stays put.
- Id
- MOBILE_HORIZONTAL_OVERFLOW
- Fix estimate
- 20 min per page
Half the text arrives only after the script
Part of the main content is added by JavaScript: move the part that belongs to the article into the server response.
About half of the page's text arrives only with the script. The server sends a skeleton with part of the content, and the browser fetches the rest: descriptions, reviews, specifications. Google sees the half first and judges that half; the other half arrives later, if at all. Usually it is a shop where the product page loads its description with a separate request, or a page with "show more" sections whose content does not exist until someone clicks. What is meant to rank has to be in the HTML from the start.
- Id
- PARTIAL_HYDRATION
- Fires at
- < 0.8 %
- Fix estimate
- 30 min per page
Page blocks pinch to zoom
Remove user-scalable=no and any maximum-scale of 1 from the viewport tag: people who need to zoom currently cannot.
- Id
- VIEWPORT_BLOCKS_ZOOM
- Fix estimate
- 10 min per page
Viewport is a fixed width
Use width=device-width so the layout follows the screen instead of a number chosen years ago.
- Id
- VIEWPORT_FIXED_WIDTH
- Fix estimate
- 10 min per page
JavaScript console errors
- Id
- CONSOLE_ERROR_ON_PAGE
- Fix estimate
- 20 min per page
Missing doctype declaration
Start the document with <!doctype html> so browsers render in standards mode instead of quirks mode.
- Id
- MISSING_DOCTYPE
- Fix estimate
- 3 min per page