Skip to content
← All documentation
19 checks

Mobile / JS

Viewport, JavaScript rendering and the gap between the HTML you serve and the page a crawler finally sees.

Critical

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
HighNeeds JS render

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
HighNeeds JS render

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
HighNeeds JS render

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
HighNeeds JS renderEstimate

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
MediumNeeds JS render

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
MediumNeeds JS render

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
MediumNeeds JS render

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
MediumNeeds JS render

H1 present only after JS renders

Id
H1_ONLY_IN_RENDERED_HTML
Fix estimate
20 min per page
MediumNeeds JS render

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
Medium

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
Medium

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
MediumEstimate

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
MediumNeeds JS render

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
Medium

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
Medium

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
Medium

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
LowNeeds JS render

JavaScript console errors

Id
CONSOLE_ERROR_ON_PAGE
Fix estimate
20 min per page
Low

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
See it on your own site

Which of these would your site trip?

Read your site, free