{"id":166763,"date":"2023-09-21T02:52:00","date_gmt":"2023-09-21T07:52:00","guid":{"rendered":"https:\/\/ahrefs.com\/blog\/?p=166763"},"modified":"2025-05-09T13:44:43","modified_gmt":"2025-05-09T18:44:43","slug":"javascript-seo","status":"publish","type":"post","link":"https:\/\/ahrefs.com\/blog\/javascript-seo\/","title":{"rendered":"JavaScript SEO Issues &amp; Best Practices"},"content":{"rendered":"\n<div class=\"intro-txt\">Did you know that while the Ahrefs Blog is powered by WordPress, much of the rest of the site is powered by JavaScript like&nbsp;React?<\/div>\n\n\n\n<p>The reality of the current web is that JavaScript is everywhere. Most websites use some kind of JavaScript to add interactivity and improve user experience.<\/p>\n\n\n\n<p>Yet most of the JavaScript used on so many websites won\u2019t impact SEO at all. If you have a normal WordPress install without a lot of customization, then likely none of the issues will apply to&nbsp;you.<\/p>\n\n\n\n<p>Where you will run into issues is when JavaScript is used to build an entire page, add or take away elements, or change what was already on the page. Some sites use it for menus, pulling in products or prices, grabbing content from multiple sources or, in some cases, for everything on the site. If this sounds like your site, keep reading.<br><br>We\u2019re seeing entire systems and apps built with JavaScript frameworks and even some traditional CMSes with a JavaScript flair where they\u2019re <a href=\"https:\/\/ahrefs.com\/blog\/headless-cms-seo\/\">headless or decoupled<\/a>. The CMS is used as the backend source of data, but the frontend presentation is handled by JavaScript.<\/p>\n<blockquote>\n<p dir=\"ltr\" lang=\"en\">The web has moved from plain HTML - as an SEO you can embrace that. Learn from JS devs &amp; share SEO knowledge with them. JS\u2019s not going&nbsp;away.<\/p>\n<p>\u2014 John \u2026 (@JohnMu)&nbsp;<a href=\"https:\/\/twitter.com\/JohnMu\/status\/894901485238190080\">August 8,&nbsp;2017<\/a><\/p>\n<\/blockquote>\n\n\n\n<p>I\u2019m not saying that SEOs need to go out and learn how to program JavaScript. I actually don\u2019t recommend it because it\u2019s not likely that you will ever touch the code. What SEOs need to know is how Google handles JavaScript and how to troubleshoot issues.&nbsp;<\/p>\n\n\n\n<div class=\"hub-link\"><img decoding=\"async\" alt=\"Beginner's guide to technical SEO\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/svg\/4.svg\"><div class=\"hl-title\">New to technical SEO? Check out&nbsp;our<\/div><div class=\"hl-content\"><a href=\"https:\/\/ahrefs.com\/blog\/technical-seo\/\" target=\"_blank\">Beginner\u2019s guide to technical SEO<\/a><\/div><\/div>\n\n\n\n<div class=\"intro-tok\" id=\"intro_tok\" style=\"display:none;\"><div class=\"intro-title\">Contents<\/div><a href=\"#\" class=\"expand-dots\"><span><\/span><span><\/span><span><\/span><\/a><\/div>\n\n\n\n<div class=\"post-nav-link clearfix\" id=\"section1\"><a class=\"subhead-anchor\" data-tip=\"tooltip__copielink\" rel=\"#section1\"><svg width=\"19\" height=\"19\" viewBox=\"0 0 14 14\" style><g fill=\"none\" fill-rule=\"evenodd\"><path d=\"M0 0h14v14H0z\" \/><path d=\"M7.45 9.887l-1.62 1.621c-.92.92-2.418.92-3.338 0a2.364 2.364 0 0 1 0-3.339l1.62-1.62-1.273-1.272-1.62 1.62a4.161 4.161 0 1 0 5.885 5.884l1.62-1.62L7.45 9.886zM5.527 5.135L7.17 3.492c.92-.92 2.418-.92 3.339 0 .92.92.92 2.418 0 3.339L8.866 8.473l1.272 1.273 1.644-1.643A4.161 4.161 0 1 0 5.897 2.22L4.254 3.863l1.272 1.272zm-.66 3.998a.749.749 0 0 1 0-1.06l2.208-2.206a.749.749 0 1 1 1.06 1.06L5.928 9.133a.75.75 0 0 1-1.061 0z\" style \/><\/g><\/svg><\/a><div class=\"link-text\" data-anchor=\"What is JavaScript SEO?\" data-section=\"what-is-javascript-seo\">\n\n\n\n<h2 class=\"wp-block-heading\">What is JavaScript SEO?<\/h2>\n\n\n\n<\/div><\/div>\n\n\n\n<p>JavaScript SEO is a part of <a href=\"https:\/\/ahrefs.com\/seo\/technical-seo\" data-ahr=\"https:\/\/ahrefs.com\/blog\/technical-seo\/\">technical SEO<\/a> (search engine optimization) that makes JavaScript-heavy websites easy to crawl and index, as well as search-friendly. The goal is to have these websites be found and <a href=\"https:\/\/ahrefs.com\/blog\/how-to-rank-higher-on-google\/\">rank higher in search engines<\/a>.<\/p>\n\n\n\n<p>JavaScript is not bad for SEO, and it\u2019s not evil. It\u2019s just different from what many SEOs are used to, and there\u2019s a bit of a learning curve.&nbsp;<\/p>\n\n\n\n<p>A lot of the processes are similar to things SEOs are already used to seeing, but there may be slight differences. You\u2019re still going to be looking at mostly HTML code, not actually looking at JavaScript.<\/p>\n\n\n\n<p>All the normal on-page SEO best practices still apply. See <a href=\"https:\/\/ahrefs.com\/seo\/on-page-seo\" data-ahr=\"https:\/\/ahrefs.com\/blog\/on-page-seo\/\">our guide on on-page SEO<\/a>.<\/p>\n\n\n\n<p>You\u2019ll even find familiar plugin-type options to handle a lot of the basic SEO elements, if it\u2019s not already built into the framework you\u2019re using. For JavaScript frameworks, these are called modules, and you\u2019ll find lots of package options to install them.<\/p>\n\n\n\n<p>There are versions for many of the popular frameworks like <a href=\"https:\/\/ahrefs.com\/blog\/react-seo\/\">React<\/a>, Vue, Angular, and Svelte that you can find by searching for the framework + module name like \u201cReact Helmet.\u201d Meta tags, Helmet, and Head are all popular modules with similar functionality and allow for many of the popular tags needed for SEO to be&nbsp;set.<\/p>\n\n\n\n<p>In some ways, JavaScript is better than traditional HTML, such as ease of building and performance. In some ways, JavaScript is worse, such as it can\u2019t be parsed progressively (like HTML and CSS can be), and it can be heavy on page load and performance. Often, you may be trading performance for functionality.<\/p>\n\n\n\n<p>JavaScript isn\u2019t perfect, and it isn\u2019t always the right tool for the job. Developers do overuse it for things where there\u2019s probably a better solution. But sometimes, you have to work with what you are&nbsp;given.<\/p>\n\n\n\n<div class=\"post-nav-link clearfix\" id=\"section1\"><a class=\"subhead-anchor\" data-tip=\"tooltip__copielink\" rel=\"#section1\"><svg width=\"19\" height=\"19\" viewBox=\"0 0 14 14\" style><g fill=\"none\" fill-rule=\"evenodd\"><path d=\"M0 0h14v14H0z\" \/><path d=\"M7.45 9.887l-1.62 1.621c-.92.92-2.418.92-3.338 0a2.364 2.364 0 0 1 0-3.339l1.62-1.62-1.273-1.272-1.62 1.62a4.161 4.161 0 1 0 5.885 5.884l1.62-1.62L7.45 9.886zM5.527 5.135L7.17 3.492c.92-.92 2.418-.92 3.339 0 .92.92.92 2.418 0 3.339L8.866 8.473l1.272 1.273 1.644-1.643A4.161 4.161 0 1 0 5.897 2.22L4.254 3.863l1.272 1.272zm-.66 3.998a.749.749 0 0 1 0-1.06l2.208-2.206a.749.749 0 1 1 1.06 1.06L5.928 9.133a.75.75 0 0 1-1.061 0z\" style \/><\/g><\/svg><\/a><div class=\"link-text\" data-anchor=\"JavaScript SEO issues and best practices\" data-section=\"javascript-seo-issues-and-best-practices\">\n\n\n\n<h2 class=\"wp-block-heading\">JavaScript SEO issues and best practices<\/h2>\n\n\n\n<\/div><\/div>\n\n\n\n<p>These are many of the common SEO issues you may run into when working with JavaScript sites.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Have unique title tags and meta descriptions<\/h3>\n\n\n\n<p>You\u2019re still going to want to have unique <a href=\"https:\/\/ahrefs.com\/blog\/title-tag-seo\/\">title tags<\/a> and <a href=\"https:\/\/ahrefs.com\/blog\/meta-description-study\/\">meta descriptions<\/a> across your pages. Because a lot of the JavaScript frameworks are templatized, you can easily end up in a situation where the same title or meta description is used for all pages or a group of&nbsp;pages.<\/p>\n\n\n\n<p>Check the <strong>Duplicates<\/strong> report in Ahrefs\u2019 <a href=\"https:\/\/ahrefs.com\/site-audit\">Site Audit<\/a> and click into any of the groupings to see more data about the issues we&nbsp;found.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"907\" class=\"wp-image-166773\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png\" alt=\"Checking for duplicate title tags and meta descriptions\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2-680x309.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2-768x348.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2-1536x697.png 1536w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<p>You can use one of the SEO modules like Helmet to set custom tags for each&nbsp;page.<\/p>\n\n\n\n<p>JavaScript can also be used to overwrite default values you may have set. Google will process this and use the overwritten title or description. For users, however, titles can be problematic, as one title may appear in the browser and they\u2019ll notice a flash when it gets overwritten.<\/p>\n\n\n\n<p>If you see the title flashing, you can use Ahrefs\u2019 <a href=\"https:\/\/ahrefs.com\/seo-toolbar\">SEO Toolbar<\/a> to see both the raw HTML and rendered versions.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1513\" height=\"620\" class=\"wp-image-166775\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image29-1.png\" alt=\"Raw and rendered titles and meta descriptions in Ahrefs' SEO Toolbar\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image29-1.png 1513w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image29-1-680x279.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image29-1-768x315.png 768w\" sizes=\"auto, (max-width: 1513px) 100vw, 1513px\"><\/figure>\n\n\n\n<p>Google may not use your titles or meta descriptions anyway. As I mentioned, the titles are worth cleaning up for users. Fixing this for meta descriptions won\u2019t really make a difference, though.<\/p>\n\n\n\n<p>When we studied Google\u2019s rewriting, we found that <a href=\"https:\/\/ahrefs.com\/blog\/title-tags-study\/\">Google overwrites titles 33.4% of the time<\/a> and <a href=\"https:\/\/ahrefs.com\/blog\/meta-description-study\/\">meta descriptions 62.78% of the time<\/a>. In Site Audit, we\u2019ll even show you which of your title tags Google has changed.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"672\" class=\"wp-image-166777\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9.png\" alt=\"&quot;Page and SERP titles do not match&quot; issue in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9-680x229.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9-768x258.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9-1536x516.png 1536w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image4-9-650x220.png 650w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Canonical tag issues<\/h3>\n\n\n\n<p>For years, Google said it didn\u2019t respect <a href=\"https:\/\/ahrefs.com\/blog\/canonical-tags\/\">canonical tags<\/a> inserted with JavaScript. It finally added an exception to the documentation for cases where there wasn\u2019t already a tag. I caused that change. I ran tests to show this worked when Google was telling everyone it didn\u2019t.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\">\n<div class=\"wp-block-embed__wrapper\">https:\/\/twitter.com\/patrickstox\/status\/1025482596459982849<\/div>\n<\/figure>\n\n\n\n<p>If there was already a canonical tag present and you add another one or overwrite the existing one with JavaScript, then you\u2019re giving them two canonical tags. In this case, Google has to figure out which one to use or ignore the canonical tags in favor of other <a href=\"https:\/\/ahrefs.com\/blog\/canonicalization\/\">canonicalization signals<\/a>.<\/p>\n\n\n\n<p>Standard SEO advice of \u201cevery page should have a self-referencing canonical tag\u201d gets many SEOs in trouble. A dev takes that requirement, and they make pages with and without a trailing slash self-canonical.<\/p>\n\n\n\n<p><code>example.com\/page<\/code> with a canonical of <code>example.com\/page<\/code> and <code>example.com\/page\/<\/code> with a canonical of <code>example.com\/page\/<\/code>. Oops, that\u2019s wrong! You probably want to redirect one of those versions to the&nbsp;other.<\/p>\n\n\n\n<p>The same thing can happen with parameterized versions that you may want to combine, but each is self-referencing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Google will use the most restrictive directives<\/h3>\n\n\n\n<p>With <a href=\"https:\/\/ahrefs.com\/blog\/meta-robots\/\">meta robots tags<\/a>, Google is always going to take the most restrictive option it sees\u2014no matter the location.<\/p>\n\n\n\n<p>If you have an index tag in the raw HTML and noindex tag in the rendered HTML, Google will treat it as noindex. If you have a noindex tag in the raw HTML but you overwrite it with an index tag using JavaScript, it\u2019s still going to treat that page as noindex. In fact, the noindex page won\u2019t even go to the renderer.<\/p>\n\n\n\n<p>It works the same for nofollow tags. Google is going to take the most restrictive option.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Set alt attributes on images<\/h3>\n\n\n\n<p>Missing <a href=\"https:\/\/ahrefs.com\/blog\/alt-text\/\">alt attributes<\/a> are an accessibility issue, which may turn into a legal issue. Most big companies have been sued for ADA compliance issues on their websites, and some get sued multiple times a year. I\u2019d fix this for the main content images, but not for things like placeholder or decorative images where you can leave the alt attributes blank.<\/p>\n\n\n\n<p>For web search, the text in alt attributes counts as text on the page, but that\u2019s really the only role it plays. Its importance is often overstated for SEO, in my opinion. However, it does help with image search and image rankings.<\/p>\n\n\n\n<p>Lots of JavaScript developers leave alt attributes blank, so double-check that yours are there. Look at the <strong>Images<\/strong> report in <a href=\"https:\/\/ahrefs.com\/site-audit\">Site Audit<\/a> to find&nbsp;these.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1063\" height=\"692\" class=\"wp-image-166779\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image31-1.png\" alt=\"Checking for missing alt attributes on JavaScript-powered sites\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image31-1.png 1063w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image31-1-653x425.png 653w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image31-1-768x500.png 768w\" sizes=\"auto, (max-width: 1063px) 100vw, 1063px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Allow crawling of JavaScript files<\/h3>\n\n\n\n<p>Don\u2019t block access to resources if they are needed to build part of the page or add to the content. Google needs to access and download resources so that it can render the pages properly. In your<a href=\"https:\/\/ahrefs.com\/blog\/robots-txt\/\"> robots.txt<\/a>, the easiest way to allow the needed resources to be crawled is to&nbsp;add:<\/p>\n\n\n\n<p><code>User-Agent: Googlebot<\/code><br><code>Allow: .js<\/code><br><code>Allow: .css<\/code><\/p>\n\n\n\n<p>Also check the robots.txt files for any subdomains or additional domains you may be making requests from, such as those for your API&nbsp;calls.<\/p>\n\n\n\n<p>If you have blocked resources with robots.txt, you can check if it impacts the page content using the block options in the \u201cNetwork\u201d tab in Chrome Dev Tools. Select the file and block it, then reload the page to see if any changes were&nbsp;made.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"489\" height=\"373\" class=\"wp-image-166781\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image15-7.png\" alt=\"&quot;Block request URL&quot; option in dropdown\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Check if Google sees your content<\/h3>\n\n\n\n<p>Many pages with JavaScript functionality may not be showing all of the content to Google by default. If you talk to your developers, they may refer to this as being not Document Object Model (DOM) loaded. This means the content wasn\u2019t loaded by default and might be loaded later with an action like a&nbsp;click.<\/p>\n\n\n\n<p>A quick check you can do is to simply search for a snippet of your content in Google inside quotation marks. Search for \u201csome phrase from your content\u201d and see if the page is returned in the search results. If it is, then your content was likely seen.<\/p>\n\n\n\n<div class=\"sidenote\"><div class=\"sidenote-title\">Sidenote.<\/div> Content that is hidden by default may not be shown within your snippet on the<a href=\"https:\/\/ahrefs.com\/blog\/serps\/\"> SERPs<\/a>. It\u2019s especially important to check your mobile version, as this is often stripped down for user experience.<\/div>\n\n\n\n<p>You can also right-click and use the \u201cInspect\u201d option. Search for the text within the \u201cElements\u201d tab.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1217\" height=\"941\" class=\"wp-image-166783\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image5-11.png\" alt=\"Searching for text in the DOM when working with JavaScript websites\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image5-11.png 1217w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image5-11-550x425.png 550w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image5-11-768x594.png 768w\" sizes=\"auto, (max-width: 1217px) 100vw, 1217px\"><\/figure>\n\n\n\n<p>The best check is going to be searching within the content of one of Google\u2019s testing tools like the URL Inspection tool in Google Search Console. I\u2019ll talk more about this&nbsp;later.<\/p>\n\n\n\n<p>I\u2019d definitely check anything behind an accordion or a dropdown. Often, these elements make requests that load content into the page when they are clicked on. Google doesn\u2019t click, so it doesn\u2019t see the content.<\/p>\n\n\n\n<p>If you use the inspect method to search content, make sure to copy the content and then reload the page or open it in an incognito window before searching.<\/p>\n\n\n\n<p>If you\u2019ve clicked the element and the content loaded in when that action was taken, you\u2019ll find the content. You may not see the same result with a fresh load of the&nbsp;page.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Duplicate content issues<\/h3>\n\n\n\n<p>With JavaScript, there may be several URLs for the same content, which leads to <a href=\"https:\/\/ahrefs.com\/blog\/duplicate-content\/\">duplicate content<\/a> issues. This may be caused by capitalization, trailing slashes, IDs, parameters with IDs, etc. So all of these may&nbsp;exist:<\/p>\n\n\n\n<p><code>domain.com\/Abc<\/code><br><code>domain.com\/abc<\/code><br><code>domain.com\/123<\/code><br><code>domain.com\/?id=123<\/code><\/p>\n\n\n\n<p>If you only want one version indexed, you should set a self-referencing canonical and either canonical tags from other versions that reference the main version or ideally redirect the other versions to the main version.<\/p>\n\n\n\n<p>Check the <strong>Duplicates<\/strong> report in <a href=\"https:\/\/ahrefs.com\/site-audit\">Site Audit<\/a>. We break down which duplicate clusters have canonical tags set and which have issues.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"887\" class=\"wp-image-166785\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image7-8.png\" alt=\"Duplicate content clusters within Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image7-8.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image7-8-680x302.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image7-8-768x341.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image7-8-1536x682.png 1536w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<p>A common issue with JavaScript frameworks is that pages can exist with and without the trailing slash. Ideally, you\u2019d pick the version you prefer and make sure that version has a self-referencing canonical tag and then redirect the other version to your preferred version.<\/p>\n\n\n\n<p>With app shell models, very little content and code may be shown in the initial HTML response. In fact, every page on the site may display the same code, and this code may be the exact same as the code on some other websites.&nbsp;<\/p>\n\n\n\n<p>If you see a lot of URLs with a low word count in Site Audit, it may indicate you have this&nbsp;issue.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"631\" class=\"wp-image-166787\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image22-2.png\" alt=\"URLs by word count report in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image22-2.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image22-2-680x215.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image22-2-768x242.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image22-2-1536x485.png 1536w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<p>This can sometimes cause pages to be treated as duplicates and not immediately go to rendering. Even worse, the wrong page or even the wrong site may show in search results. This should resolve itself over time but can be problematic, especially with newer websites.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Don\u2019t use fragments (#) in&nbsp;URLs<\/h3>\n\n\n\n<p># already has a defined functionality for browsers. It links to another part of a page when clicked\u2014like our \u201ctable of contents\u201d feature on the blog. Servers generally won\u2019t process anything after a #. So for a URL like <code>abc.com\/#something<\/code>, anything after a # is typically ignored.&nbsp;<\/p>\n\n\n\n<p>JavaScript developers have decided they want to use # as the trigger for different purposes, and that causes confusion. The most common ways they\u2019re misused are for routing and for <a href=\"https:\/\/ahrefs.com\/blog\/url-parameters\/\">URL parameters<\/a>. Yes, they work. No, you shouldn\u2019t do&nbsp;it.<\/p>\n\n\n\n<p>JavaScript frameworks typically have routers that map what they call routes (paths) to <a href=\"https:\/\/ahrefs.com\/blog\/seo-friendly-urls\/\">clean URLs<\/a>. A lot of JavaScript developers use hashes (#) for routing. This is especially a problem for Vue and some of the earlier versions of Angular.&nbsp;<\/p>\n\n\n\n<p>To fix this for Vue, you can work with your developer to change the following:<\/p>\n\n\n\n<p><code>Vue router:&nbsp;<\/code><br><code>Use \u2018History\u2019 Mode instead of the traditional \u2018Hash\u2019 Mode.<\/code><\/p>\n\n\n\n<p><code>const router = new VueRouter ({<\/code><br><code>mode: \u2018history\u2019,<\/code><br><code>router: [] \/\/the array of router links<\/code><br><code>)}<\/code><\/p>\n\n\n\n<p>There\u2019s a growing trend where people are using # instead of ? as the fragment identifier, especially for passive URL parameters like those used for tracking. I tend to recommend against it because of all of the confusion and issues. Situationally, I might be OK with it getting rid of a lot of unnecessary parameters.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Create a sitemap<\/h3>\n\n\n\n<p>The router options that allow for clean URLs usually have an additional module that can also create sitemaps. You can find them by searching for your system + router sitemap, such as \u201cVue router sitemap.\u201d<\/p>\n\n\n\n<p>Many of the rendering solutions may also have sitemap options. Again, just find the system you use and Google the system + sitemap such as \u201cGatsby sitemap,\u201d and you\u2019re sure to find a solution that already exists.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Status codes and soft&nbsp;404s<\/h3>\n\n\n\n<p>Because JavaScript frameworks aren\u2019t server-side, they can\u2019t really throw a server error like a 404. You have a couple of different options for error pages, such&nbsp;as:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Using a JavaScript redirect to a page that does respond with a 404 status code.<\/li>\n\n\n\n<li>Adding a noindex tag to the page that\u2019s failing along with some kind of error message like \u201c404 Page Not Found.\u201d This will be treated as a soft 404 since the actual status code returned will be a 200&nbsp;okay.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">JavaScript redirects are OK, but not preferred<\/h3>\n\n\n\n<p>SEOs are used to<a href=\"https:\/\/ahrefs.com\/blog\/301-redirects\/\"> 301\/302 redirects<\/a>, which are server-side. JavaScript is typically run client-side. Server-side redirects and even meta refresh redirects will be easier for Google to process than JavaScript redirects since it won\u2019t have to render the page to see&nbsp;them.<\/p>\n\n\n\n<p>JavaScript redirects will still be seen and processed during rendering and should be OK in most cases\u2014they\u2019re just not as ideal as other redirect types. They are treated as permanent redirects and still pass all signals like<a href=\"https:\/\/ahrefs.com\/blog\/google-pagerank\/\"> PageRank<\/a>.<\/p>\n\n\n\n<p>You can often find these redirects in the code by looking for \u201cwindow.location.href\u201d. The redirects could potentially be in the config file as well. In the Next.js config, there\u2019s a redirect function you can use to set redirects. In other systems, you may find them in the router.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Internationalization issues<\/h3>\n\n\n\n<p>There are usually a few module options for different frameworks that support some features needed for internationalization like<a href=\"https:\/\/ahrefs.com\/blog\/hreflang-tags\/\"> hreflang<\/a>. They\u2019ve commonly been ported to the different systems and include i18n, intl or, many times, the same modules used for header tags like Helmet can be used to add the needed tags.<\/p>\n\n\n\n<p>We flag hreflang issues in the <strong>Localization<\/strong> report in <a href=\"https:\/\/ahrefs.com\/site-audit\">Site Audit<\/a>. We also ran a study and found that <a href=\"https:\/\/ahrefs.com\/blog\/hreflang-study\/\">67% of domains using hreflang have issues<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"1900\" class=\"wp-image-166790\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image30-2.png\" alt=\"Hreflang issues shown in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image30-2.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image30-2-447x425.png 447w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image30-2-768x730.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image30-2-1536x1460.png 1536w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<p>You also need to be wary if your site is blocking or treating visitors from a specific country or using a particular IP in different ways. This can cause your content not to be seen by<a href=\"https:\/\/ahrefs.com\/blog\/googlebot\/\"> Googlebot<\/a>. If you have logic redirecting users, you may want to exclude bots from this&nbsp;logic.<\/p>\n\n\n\n<p>We\u2019ll let you know if this is happening when setting up a project in Site&nbsp;Audit.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1914\" height=\"498\" class=\"wp-image-166792\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image8-9.png\" alt=\"Checking if JavaScript site is being redirected\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image8-9.png 1914w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image8-9-680x177.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image8-9-768x200.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image8-9-1536x400.png 1536w\" sizes=\"auto, (max-width: 1914px) 100vw, 1914px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Use structured data<\/h3>\n\n\n\n<p>JavaScript can be used to generate or to inject structured data on your pages. It\u2019s pretty common to do this with JSON-LD and not likely to cause any issues, but run some tests to make sure everything comes out like you expect.<\/p>\n\n\n\n<p>We\u2019ll flag any structured data we see in the <strong>Issues<\/strong> report in <a href=\"https:\/\/ahrefs.com\/site-audit\">Site Audit<\/a>. Look for the \u201cStructured data has schema.org validation\u201d error. We\u2019ll tell you exactly what is wrong for each&nbsp;page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1271\" height=\"1082\" class=\"wp-image-166793\" style=\"object-fit: cover; width: 572px; height: 487px;\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image18-2.png\" alt=\"Making sure schema markup is valid\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image18-2.png 1271w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image18-2-499x425.png 499w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image18-2-768x654.png 768w\" sizes=\"auto, (max-width: 1271px) 100vw, 1271px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Use standard format links<\/h3>\n\n\n\n<p>Links to other pages should be in the web standard format. Internal and external links need to be an <code>&lt;a&gt;<\/code> tag with an <code>href<\/code> attribute. There are many ways you can make links work for users with JavaScript that are not search-friendly.<\/p>\n\n\n\n<p><strong>Good:<\/strong><\/p>\n\n\n\n<p><code>&lt;a href=\u201d\/page\u201d&gt;simple is good&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;a href=\u201d\/page\u201d onclick=\u201dgoTo(\u2018page\u2019)\u201d&gt;still okay&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p><strong>Bad:<\/strong><\/p>\n\n\n\n<p><code>&lt;a onclick=\u201dgoTo(\u2018page\u2019)\u201d&gt;nope, no href&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;a href=\u201djavascript:goTo(\u2018page\u2019)\u201d&gt;nope, missing link&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;a href=\u201djavascript:void(0)\u201d&gt;nope, missing link&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;span onclick=\u201dgoTo(\u2018page\u2019)\u201d&gt;not the right HTML element&lt;\/span&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;option value=\"page\"&gt;nope, wrong HTML element&lt;\/option&gt;<\/code><\/p>\n\n\n\n<p><code>&lt;a href=\u201d#\u201d&gt;no link&lt;\/a&gt;<\/code><\/p>\n\n\n\n<p>Button, ng-click, there are many more ways this can be done incorrectly.<\/p>\n\n\n\n<p>In my experience, Google still processes many of the bad links and crawls them, but I\u2019m not sure how it treats them as far passing signals like PageRank. The web is a messy place, and Google\u2019s parsers are often fairly forgiving.<\/p>\n\n\n\n<p>It\u2019s also worth noting that<a href=\"https:\/\/ahrefs.com\/blog\/internal-links-for-seo\/\"> internal links<\/a> added with JavaScript will not get picked up until after rendering. That should be relatively quick and not a cause for concern in most&nbsp;cases.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Use file versioning to solve for impossible states being indexed<\/h3>\n\n\n\n<p>Google heavily caches all resources on its end. I\u2019ll talk about this a bit more later, but you should know that its system can lead to some impossible states being indexed. This is a quirk of its systems. In these cases, previous file versions are used in the rendering process, and the indexed version of a page may contain parts of older&nbsp;files.<\/p>\n\n\n\n<p>You can use file versioning or fingerprinting (file.12345.js) to generate new file names when significant changes are made so that Google has to download the updated version of the resource for rendering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">You may not see what is shown to Googlebot<\/h3>\n\n\n\n<p>You may need to change your user-agent to properly diagnose some issues. Content can be rendered differently for different user-agents or even IPs. You should check what Google actually sees with its testing tools, and I\u2019ll cover those in a&nbsp;bit.<\/p>\n\n\n\n<p>You can set a custom user-agent with Chrome DevTools to troubleshoot sites that prerender based on specific user-agents, or you can easily do this with <a href=\"https:\/\/ahrefs.com\/seo-toolbar\">our toolbar<\/a> as&nbsp;well.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1014\" height=\"610\" class=\"wp-image-166794\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image14-7.png\" alt=\"Switching user-agent to troubleshoot SEO issues on JavaScript sites\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image14-7.png 1014w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image14-7-680x409.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image14-7-768x462.png 768w\" sizes=\"auto, (max-width: 1014px) 100vw, 1014px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Use polyfills for unsupported features<\/h3>\n\n\n\n<p>There can be features used by developers that Googlebot doesn\u2019t support. Your developers can use<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Learn\/Tools_and_testing\/Cross_browser_testing\/Feature_detection\"> feature detection<\/a>. And if there\u2019s a missing feature, they can choose to either skip that functionality or use a fallback method with a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Polyfill\">polyfill<\/a> to see if they can make it&nbsp;work.<\/p>\n\n\n\n<p>This is mostly an FYI for SEOs. If you see something you think Google should be seeing and it\u2019s not seeing it, it could be because of the implementation.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Use lazy loading<\/h3>\n\n\n\n<p>Since I originally wrote this, lazy loading has mostly moved from being JavaScript-driven to being handled by browsers.<\/p>\n\n\n\n<p>You may still run into some JavaScript-driven lazy load setups. For the most part, they\u2019re probably fine if the lazy loading is for images. The main thing I\u2019d check is to see if content is being lazy loaded. Refer back to the \u201cCheck if Google sees your content\u201d section above. These kinds of setups have caused problems with the content being picked up correctly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Infinite scroll issues<\/h3>\n\n\n\n<p>If you have an infinite scroll setup, I still recommend a paginated page version so that Google can still crawl properly.<\/p>\n\n\n\n<p>Another issue I\u2019ve seen with this setup is, occasionally, two pages get indexed as one. I\u2019ve seen this a few times when people said they couldn\u2019t get their page indexed. But I\u2019ve found their content indexed as part of another page that\u2019s usually the previous post from&nbsp;them.<\/p>\n\n\n\n<p>My theory is that when Google resized the viewport to be longer (more on this later), it triggered the infinite scroll and loaded another article in when it was rendering. In this case, what I recommend is to block the JavaScript file that handles the infinite scrolling so the functionality can\u2019t trigger.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Performance issues<\/h3>\n\n\n\n<p>A lot of the JavaScript frameworks take care of a ton of modern performance optimization for&nbsp;you.<\/p>\n\n\n\n<p>All of the traditional performance best practices still apply, but you get some fancy new options. Code splitting chunks the files into smaller files. Tree shaking breaks out needed parts, so you\u2019re not loading everything for every page like you\u2019d see in traditional monolithic setups.&nbsp;<\/p>\n\n\n\n<p>JavaScript setups done well are a thing of beauty. JavaScript setups that aren\u2019t done well can be bloated and cause long load&nbsp;times.<\/p>\n\n\n\n<p>Check out our <a href=\"https:\/\/ahrefs.com\/blog\/core-web-vitals\/\">Core Web Vitals guide<\/a> for more about website performance.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JavaScript sites use more crawl budget&nbsp;<\/h3>\n\n\n\n<p>JavaScript XHR requests eat <a href=\"https:\/\/ahrefs.com\/blog\/crawl-budget\/\">crawl budget<\/a>, and I mean they gobble it down. Unlike most other resources that are cached, these get fetched live during the rendering process.<\/p>\n\n\n\n<p>Another interesting detail is that the rendering service tries not to fetch resources that don\u2019t contribute to the content of the page. If it gets this wrong, you may be missing some content.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Workers aren\u2019t supported, or are&nbsp;they?<\/h3>\n\n\n\n<p>While Google historically says that it rejects service workers and service workers can\u2019t edit the DOM, Google\u2019s own Martin Splitt indicated that you may get away with using web workers sometimes.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-twitter wp-block-embed-twitter\">\n<div class=\"wp-block-embed__wrapper\">https:\/\/twitter.com\/g33konaut\/status\/1134013632931667968<\/div>\n<\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Use HTTP connections<\/h3>\n\n\n\n<p>Googlebot supports HTTP requests but doesn\u2019t support other connection types like <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/WebSockets_API\">WebSockets<\/a> or <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/WebRTC\">WebRTC<\/a>. If you\u2019re using those, provide a fallback that uses HTTP connections.<\/p>\n\n\n\n<div class=\"post-nav-link clearfix\" id=\"section1\"><a class=\"subhead-anchor\" data-tip=\"tooltip__copielink\" rel=\"#section1\"><svg width=\"19\" height=\"19\" viewBox=\"0 0 14 14\" style><g fill=\"none\" fill-rule=\"evenodd\"><path d=\"M0 0h14v14H0z\" \/><path d=\"M7.45 9.887l-1.62 1.621c-.92.92-2.418.92-3.338 0a2.364 2.364 0 0 1 0-3.339l1.62-1.62-1.273-1.272-1.62 1.62a4.161 4.161 0 1 0 5.885 5.884l1.62-1.62L7.45 9.886zM5.527 5.135L7.17 3.492c.92-.92 2.418-.92 3.339 0 .92.92.92 2.418 0 3.339L8.866 8.473l1.272 1.273 1.644-1.643A4.161 4.161 0 1 0 5.897 2.22L4.254 3.863l1.272 1.272zm-.66 3.998a.749.749 0 0 1 0-1.06l2.208-2.206a.749.749 0 1 1 1.06 1.06L5.928 9.133a.75.75 0 0 1-1.061 0z\" style \/><\/g><\/svg><\/a><div class=\"link-text\" data-anchor=\"JavaScript SEO testing tools and troubleshooting\" data-section=\"javascript-seo-testing-tools-and-troubleshooting\">\n\n\n\n<h2 class=\"wp-block-heading\">JavaScript SEO testing tools and troubleshooting<\/h2>\n\n\n\n<\/div><\/div>\n\n\n\n<p>One \u201cgotcha\u201d with JavaScript sites is they can do partial updates of the DOM. Browsing to another page as a user may not update some aspects like title tags or canonical tags in the DOM, but this may not be an issue for search engines.<\/p>\n\n\n\n<p>Google loads each page stateless like it\u2019s a fresh load. It\u2019s not saving previous information and not navigating between pages.<\/p>\n\n\n\n<p>I\u2019ve seen SEOs get tripped up thinking there is a problem because of what they see after navigating from one page to another, such as a canonical tag that doesn\u2019t update. But Google may never see this&nbsp;state.<\/p>\n\n\n\n<p>Devs can fix this by updating the state using what\u2019s called the<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/History_API\"> History API<\/a>. But again, it may not be a problem. A lot of time, it\u2019s just SEOs making trouble for the developers because it looks weird to them. Refresh the page and see what you see. Or better yet, run it through one of Google\u2019s testing tools to see what it&nbsp;sees.<\/p>\n\n\n\n<p>Speaking of its testing tools, let\u2019s talk about&nbsp;those.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Google testing tools<\/h3>\n\n\n\n<p>Google has several testing tools that are useful for JavaScript.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">URL Inspection tool in Google Search Console<\/h4>\n\n\n\n<p>This should be your source of truth. When you inspect a URL, you\u2019ll get a lot of info about what Google saw and the actual rendered HTML from its system.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1890\" height=\"815\" class=\"wp-image-166796\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image25-1.png\" alt=\"Using URL Inspection tool to see what Google sees after it processes JavaScript\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image25-1.png 1890w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image25-1-680x293.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image25-1-768x331.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image25-1-1536x662.png 1536w\" sizes=\"auto, (max-width: 1890px) 100vw, 1890px\"><\/figure>\n\n\n\n<p>You have the option to run a live test as&nbsp;well.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1999\" height=\"800\" class=\"wp-image-166797\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image2-8.png\" alt=\"&quot;Test Live URL&quot; option in Google Search Console\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image2-8.png 1999w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image2-8-680x272.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image2-8-768x307.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image2-8-1536x615.png 1536w\" sizes=\"auto, (max-width: 1999px) 100vw, 1999px\"><\/figure>\n\n\n\n<p>There are some differences between the main renderer and the live test. The renderer uses cached resources and is fairly patient. The live test and other testing tools use live resources, and they cut off rendering early because you\u2019re waiting for a result. I\u2019ll go into more detail about this in the rendering section later.<\/p>\n\n\n\n<p>The screenshots in these tools also show pages with the pixels painted, which Google doesn\u2019t actually do when rendering a&nbsp;page.<\/p>\n\n\n\n<p>The tools are useful to see if content is DOM-loaded. The HTML shown in these tools is the rendered DOM. You can search for a snippet of text to see if it was loaded in by default.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1175\" height=\"984\" class=\"wp-image-166799\" style=\"object-fit: cover; width: 528px; height: 443px;\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image9-7.png\" alt=\"Searching for text within the DOM to make sure it's loaded by default on JavaScript sites\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image9-7.png 1175w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image9-7-507x425.png 507w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image9-7-768x643.png 768w\" sizes=\"auto, (max-width: 1175px) 100vw, 1175px\"><\/figure>\n\n\n\n<p>The tools will also show you resources that may be blocked and console error messages, which are useful for debugging.<\/p>\n\n\n\n<p>If you don\u2019t have access to the Google Search Console property for a website, you can still run a live test on it. If you add a redirect on your own website on a property where you have Google Search Console access, then you can inspect that URL and the inspection tool will follow the redirect and show you the live test result for the page on the other domain.&nbsp;<\/p>\n\n\n\n<p>In the screenshot below, I added a redirect from my site to Google\u2019s homepage. The live test for this follows the redirect and shows me Google\u2019s homepage. I do not actually have access to Google\u2019s Google Search Console account, although I wish I&nbsp;did.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1913\" height=\"1215\" class=\"wp-image-166801\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2.png\" alt=\"A hack to test a URL on a website you don't own\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2.png 1913w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2-669x425.png 669w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2-260x166.png 260w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2-768x488.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image28-2-1536x976.png 1536w\" sizes=\"auto, (max-width: 1913px) 100vw, 1913px\"><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Rich Results Test&nbsp;tool<\/h4>\n\n\n\n<p>The <a href=\"https:\/\/search.google.com\/test\/rich-results\">Rich Results Test tool<\/a> allows you to check your rendered page as Googlebot would see it for mobile or for desktop.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Mobile-Friendly Test&nbsp;tool<\/h4>\n\n\n\n<p>You can still use the <a href=\"https:\/\/search.google.com\/test\/mobile-friendly\">Mobile-Friendly Test tool<\/a> for now, but Google has announced it is shutting down in December 2023.<\/p>\n\n\n\n<p>It has the same quirks as the other testing tools from Google.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Ahrefs<\/h3>\n\n\n\n<p>Ahrefs is the only major SEO tool that <a href=\"https:\/\/ahrefs.com\/blog\/crawling-javascript\/\">renders webpages when crawling the web<\/a>, so we have data from JavaScript sites that no other tool does. We render ~200M pages a day, but that\u2019s a fraction of what we&nbsp;crawl.<\/p>\n\n\n\n<p>It allows us to check for JavaScript redirects. We can also show links we found inserted with JavaScript, which we show with a JS tag in the link reports:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"459\" height=\"319\" class=\"wp-image-166802\" style=\"object-fit: cover; width: 344px; height: 239px;\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image16-5.png\" alt=\"Links added with JavaScript in Ahrefs' Site Explorer\"><\/figure>\n\n\n\n<p>In the drop-down menu for pages in <a href=\"https:\/\/ahrefs.com\/site-explorer\">Site Explorer<\/a>, we also have an inspect option that lets you see the history of a page and compare it to other crawls. We have a JS marker there for pages that were rendered with JavaScript enabled.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1361\" height=\"1024\" class=\"wp-image-166804\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image6-7.png\" alt=\"Pages crawled with JavaScript rendering in Ahrefs' Site Explorer\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image6-7.png 1361w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image6-7-565x425.png 565w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image6-7-768x578.png 768w\" sizes=\"auto, (max-width: 1361px) 100vw, 1361px\"><\/figure>\n\n\n\n<p>You can enable JavaScript in<a href=\"https:\/\/ahrefs.com\/site-audit\"> Site Audit<\/a> crawls to unlock more data in your audits.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1998\" height=\"1181\" class=\"wp-image-166806\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image23-2.png\" alt=\"Enabling JavaScript rendering in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image23-2.png 1998w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image23-2-680x402.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image23-2-768x454.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image23-2-1536x908.png 1536w\" sizes=\"auto, (max-width: 1998px) 100vw, 1998px\"><\/figure>\n\n<div class=\"highlighted-section\"><!-- Title Section -->\n<h4 class=\"section-title\">Check for JavaScript SEO issues for Free in Site&nbsp;Audit<\/h4>\n<!-- Form Section --><form class=\"form-section\">\n<div class=\"input-button-wrapper\">\n<div class=\"input-container\"><input type=\"text\" placeholder=\"Enter domain\"><\/div>\n<a class=\"start-button\" href=\"\/signup?plan=awt&amp;return=site-audit\">Start for&nbsp;Free<\/a><\/div>\n<div class=\"tooltip-container\">Free for websites with verified ownership<span class=\"info-icon\">i<\/span>\n<div class=\"tooltip-content\">\n<p>Ownership verification can be done&nbsp;by:<\/p>\n<ul>\n<li>Connecting Google Search Console (recommended);<\/li>\n<li>Uploading an HTML&nbsp;file;<\/li>\n<li>Adding a TXT record to your DNS configuration;<\/li>\n<li>Adding an HTML meta tag to your homepage.<\/li>\n<\/ul>\n<a href=\"https:\/\/help.ahrefs.com\/en\/articles\/3275938-verifying-ownership-of-your-project-or-website\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more<\/a><\/div>\n<\/div>\n<p class=\"webmaster-tools-info\">Signing up here gives you access to <a href=\"https:\/\/ahrefs.com\/webmaster-tools\" target=\"_blank\" rel=\"noopener noreferrer\">Ahrefs Webmaster Tools \u2197<\/a> for&nbsp;free<\/p>\n<\/form><\/div>\n<p><style>\n  \/* Highlighted Section with Gray Border *\/\n  .highlighted-section {\n    border: 1px solid #ddd; \/* Light gray border *\/\n    border-radius: 8px;\n    padding: 15px 20px; \/* Adjusted padding to reduce top spacing *\/\n    background-color: #f9f9f9; \/* Light background for contrast *\/\n    max-width: 600px;\n    margin: 20px auto; \/* Center alignment *\/\n  }\n\n  \/* Section Title *\/\n  .section-title {\n    font-size: 24px; \/* Larger font size *\/\n    font-weight: bold;\n    color: #333;\n    margin-bottom: 15px;\n    margin-top: 0; \/* Remove extra spacing above the title *\/\n    text-align: center;\n  }\n\n  .form-section {\n    display: flex;\n    flex-direction: column;\n    gap: 10px;\n  }\n\n  .input-button-wrapper {\n    display: flex;\n    gap: 10px; \/* Space between input and button *\/\n    align-items: center;\n  }\n\n  .input-container input {\n    padding: 10px;\n    font-size: 16px;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n    width: 100%;\n  }\n\n  .tooltip-container {\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    font-size: 14px;\n    color: #555;\n    cursor: help;\n  }\n\n  .tooltip-container .info-icon {\n    color: #0073e6;\n    font-weight: bold;\n    font-size: 12px;\n    position: relative;\n    top: -5px;\n    display: inline-block;\n    text-decoration: none;\n    cursor: help;\n  }\n\n  .tooltip-content {\n    position: fixed; \/* Ensure tooltip can break out of its container *\/\n    top: 50px; \/* Adjust based on trigger position *\/\n    left: 50%; \/* Center horizontally *\/\n    transform: translateX(-50%);\n    background: white;\n    border: 1px solid #ddd;\n    border-radius: 5px;\n    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);\n    padding: 15px;\n    font-size: 14px;\n    display: none; \/* Initially hidden *\/\n    z-index: 1000;\n    min-width: 300px;\n    max-width: 400px; \/* Optional width for better readability *\/\n    white-space: normal;\n  }\n\n  .tooltip-container:hover .tooltip-content {\n    display: block;\n  }\n\n  .tooltip-content ul {\n    list-style-type: disc;\n    padding-left: 20px;\n  }\n\n  .tooltip-content a {\n    color: #0073e6;\n    text-decoration: none;\n  }\n\n  .webmaster-tools-info {\n    color: #333;\n    font-size: 14px;\n    margin-top: 10px;\n  }\n\n  .start-button {\n    background-color: orange;\n    color: white;\n    padding: 10px 20px;\n    text-decoration: none;\n    font-weight: bold;\n    border-radius: 5px;\n    white-space: nowrap; \/* Ensure text doesn\u2019t break into a second line *\/\n  }\n<\/style><\/p>\n\n<p>If you have JavaScript rendering enabled, we will provide the raw and rendered HTML for every page. Use the \u201cmagnifying glass\u201d option next to a page in Page Explorer and go to \u201cView source\u201d in the menu. You can also compare against previous crawls and search within the raw or rendered HTML across all pages on the&nbsp;site.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1536\" height=\"1350\" class=\"wp-image-166807\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image20-4.png\" alt=\"Checking raw and JavaScript-rendered HTML in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image20-4.png 1536w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image20-4-484x425.png 484w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image20-4-768x675.png 768w\" sizes=\"auto, (max-width: 1536px) 100vw, 1536px\"><\/figure>\n\n\n\n<p>If you run a crawl without JavaScript and then another one with it, you can use our crawl comparison features to see differences between the versions.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1860\" height=\"1034\" class=\"wp-image-166809\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image3-4.png\" alt=\"Seeing changes between crawls in Ahrefs' Site Audit\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image3-4.png 1860w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image3-4-680x378.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image3-4-768x427.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image3-4-1536x854.png 1536w\" sizes=\"auto, (max-width: 1860px) 100vw, 1860px\"><\/figure>\n\n\n\n<p>Ahrefs\u2019 <a href=\"https:\/\/ahrefs.com\/seo-toolbar\">SEO Toolbar<\/a> also supports JavaScript and allows you to compare HTML to rendered versions of&nbsp;tags.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1020\" height=\"413\" class=\"wp-image-166811\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image24-2.png\" alt=\"Ahrefs' SEO Toolbar shows differences between raw and rendered tags like title, description, canonical\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image24-2.png 1020w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image24-2-680x275.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image24-2-768x311.png 768w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">View source vs. inspect<\/h3>\n\n\n\n<p>When you right-click in a browser window, you\u2019ll see a couple of options for viewing the source code of the page and for inspecting the page. View source is going to show you the same as a GET request would. This is the raw HTML of the&nbsp;page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"622\" height=\"75\" class=\"wp-image-166813\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image10-8.png\" alt=\"Use &quot;Inspect&quot; over &quot;View page source&quot; when troubleshooting JavaScript SEO issues\"><\/figure>\n\n\n\n<p>Inspect shows you the processed DOM after changes have been made and is closer to the content that Googlebot sees. It\u2019s the page after JavaScript has run and made changes to&nbsp;it.<\/p>\n\n\n\n<p>You should mostly use inspect over view source when working with JavaScript.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sometimes you need to check view source<\/h3>\n\n\n\n<p>Because Google looks at both raw and rendered HTML for some issues, you may still need to check view source at times. For instance, if Google\u2019s tools are telling you the page is marked noindex, but you don\u2019t see a noindex tag in the rendered HTML, it\u2019s possible that it was there in the raw HTML and overwritten.<\/p>\n\n\n\n<p>For things like noindex, nofollow, and canonical tags, you may need to check the raw HTML since issues can carry over. Remember that Google will take the most restrictive statements it saw for the meta robots tags, and it\u2019ll ignore canonical tags when you show it multiple canonical tags.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Don\u2019t browse with JavaScript turned off<\/h3>\n\n\n\n<p>I\u2019ve seen this recommended way too many times. Google renders JavaScript, so what you see without JavaScript is not at all like what Google sees. This is just&nbsp;silly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Don\u2019t use Google Cache<\/h3>\n\n\n\n<p>Google\u2019s cache is not a reliable way to check what Googlebot sees. What you typically see in the cache is the raw HTML snapshot. Your browser then fires the JavaScript that is referenced in the HTML. It\u2019s not what Google saw when it rendered the&nbsp;page.<\/p>\n\n\n\n<p>To complicate this further, websites may have their <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">Cross-Origin Resource Sharing (CORS)<\/a> policy set up in a way that the required resources can\u2019t be loaded from a different domain.<\/p>\n\n\n\n<p>The cache is hosted on webcache.googleusercontent.com. When that domain tries to request the resources from the actual domain, the CORS policy says, \u201cNope, you can\u2019t access my files.\u201d Then the files aren\u2019t loaded, and the page looks broken in the&nbsp;cache.<\/p>\n\n\n\n<p>The cache system was made to see the content when a website is down. It\u2019s not particularly useful as a debug&nbsp;tool.<\/p>\n\n\n\n<div class=\"post-nav-link clearfix\" id=\"section1\"><a class=\"subhead-anchor\" data-tip=\"tooltip__copielink\" rel=\"#section1\"><svg width=\"19\" height=\"19\" viewBox=\"0 0 14 14\" style><g fill=\"none\" fill-rule=\"evenodd\"><path d=\"M0 0h14v14H0z\" \/><path d=\"M7.45 9.887l-1.62 1.621c-.92.92-2.418.92-3.338 0a2.364 2.364 0 0 1 0-3.339l1.62-1.62-1.273-1.272-1.62 1.62a4.161 4.161 0 1 0 5.885 5.884l1.62-1.62L7.45 9.886zM5.527 5.135L7.17 3.492c.92-.92 2.418-.92 3.339 0 .92.92.92 2.418 0 3.339L8.866 8.473l1.272 1.273 1.644-1.643A4.161 4.161 0 1 0 5.897 2.22L4.254 3.863l1.272 1.272zm-.66 3.998a.749.749 0 0 1 0-1.06l2.208-2.206a.749.749 0 1 1 1.06 1.06L5.928 9.133a.75.75 0 0 1-1.061 0z\" style \/><\/g><\/svg><\/a><div class=\"link-text\" data-anchor=\"How Google processes pages with JavaScript\" data-section=\"how-google-processes-pages-with-javascript\">&nbsp;\n\n\n\n<h2 class=\"wp-block-heading\">How Google processes pages with JavaScript<\/h2>\n\n\n\n<\/div><\/div>\n\n\n\n<p>In the early days of search engines, a downloaded HTML response was enough to see the content of most pages. Thanks to the rise of JavaScript, search engines now need to render many pages as a browser would so they can see content as how a user sees&nbsp;it.<\/p>\n\n\n\n<p>The system that handles the rendering process at Google is called the Web Rendering Service (WRS). Google has provided a simplistic diagram to cover how this process works.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1374\" height=\"981\" class=\"wp-image-166815\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image19-2.png\" alt=\"Googlebot crawl render and indexing process diagram\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image19-2.png 1374w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image19-2-595x425.png 595w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image19-2-768x548.png 768w\" sizes=\"auto, (max-width: 1374px) 100vw, 1374px\">\n<figcaption class=\"wp-element-caption\">Source: <a href=\"https:\/\/developers.google.com\/search\/docs\/crawling-indexing\/javascript\/javascript-seo-basics\">Google<\/a>.<\/figcaption>\n<\/figure>\n\n\n\n<p>Let\u2019s say we start the process at&nbsp;URL.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Crawler<\/h3>\n\n\n\n<p>The crawler sends GET requests to the server. The server responds with headers and the contents of the file, which then gets saved. The headers and the content typically come in the same request.<\/p>\n\n\n\n<p>The request is likely to come from a mobile user-agent since Google is on <a href=\"https:\/\/ahrefs.com\/blog\/mobile-first-indexing\/\">mobile-first indexing<\/a> now, but it also still crawls with the desktop user-agent.&nbsp;<\/p>\n\n\n\n<p>The requests mostly come from Mountain View (CA, U.S.), but it also does <a href=\"https:\/\/support.google.com\/webmasters\/answer\/6144055?hl=en\">some crawling for locale-adaptive pages<\/a> outside of the U.S. As I mentioned earlier, this can cause issues if sites are blocking or treating visitors in a specific country in different ways.<\/p>\n\n\n\n<p>It\u2019s also important to note that while Google states the output of the crawling process as \u201cHTML\u201d on the image above, in reality, it\u2019s crawling and storing the resources needed to build the page like the HTML, JavaScript files, and CSS files. There\u2019s also a 15 MB max size limit for HTML&nbsp;files.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Processing<\/h3>\n\n\n\n<p>There are a lot of systems obfuscated by the term \u201cProcessing\u201d in the image. I\u2019m going to cover a few of these that are relevant to JavaScript.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Resources and&nbsp;links<\/h4>\n\n\n\n<p>Google does not navigate from page to page as a user would. Part of \u201cProcessing\u201d is to check the page for links to other pages and files needed to build the page. These links are pulled out and added to the crawl queue, which is what Google is using to prioritize and schedule crawling.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1800\" height=\"2340\" class=\"wp-image-166817\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1.png\" alt=\"Illustration showing Googlebot doesn't navigate like users\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1.png 1800w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1-327x425.png 327w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1-768x998.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1-1182x1536.png 1182w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-1-1575x2048.png 1575w\" sizes=\"auto, (max-width: 1800px) 100vw, 1800px\"><\/figure>\n\n\n\n<p>Google will pull resource links (CSS, JS, etc.) needed to build a page from things like <code>&lt;link&gt;<\/code> tags.<\/p>\n\n\n\n<p>As I mentioned earlier, <a href=\"https:\/\/ahrefs.com\/blog\/internal-links-for-seo\/\">internal links<\/a> added with JavaScript will not get picked up until after rendering. That should be relatively quick and not a cause for concern in most cases. Things like news sites may be the exception where every second counts.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Caching<\/h4>\n\n\n\n<p>Every file that Google downloads, including HTML pages, JavaScript files, CSS files, etc., is going to be aggressively cached. Google will ignore your cache timings and fetch a new copy when it wants to. I\u2019ll talk a bit more about this and why it\u2019s important in the \u201cRenderer\u201d section.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1800\" height=\"2640\" class=\"wp-image-166819\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2.png\" alt=\"Illustration showing Google caches pages and resources\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2.png 1800w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2-290x425.png 290w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2-768x1126.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2-1047x1536.png 1047w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-2-1396x2048.png 1396w\" sizes=\"auto, (max-width: 1800px) 100vw, 1800px\"><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Duplicate elimination<\/h4>\n\n\n\n<p>Duplicate content may be eliminated or deprioritized from the downloaded HTML before it gets sent to rendering. I already talked about this in the \u201cDuplicate content\u201d section above.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Most restrictive directives<\/h4>\n\n\n\n<p>As I mentioned earlier, Google will choose the most restrictive statements between HTML and the rendered version of a page. If JavaScript changes a statement and that conflicts with the statement from HTML, Google will simply obey whichever is the most restrictive. Noindex will override index, and noindex in HTML will skip rendering altogether.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Render queue<\/h3>\n\n\n\n<p>One of the biggest concerns from many SEOs with JavaScript and two-stage indexing (HTML then rendered page) is that pages may not get rendered for days or even weeks. When Google looked into this, it found <a href=\"https:\/\/twitter.com\/igrigorik\/status\/1194427659867967490\">pages went to the renderer at a median time of five seconds<\/a>, and the 90th percentile was minutes. So the amount of time between getting the HTML and rendering the pages should not be a concern in most&nbsp;cases.<\/p>\n\n\n\n<p>However, Google doesn\u2019t render all pages. Like I mentioned previously, a page with a robots meta tag or header containing a noindex tag will not be sent to the renderer. It won\u2019t waste resources rendering a page it can\u2019t index anyway.<\/p>\n\n\n\n<p>It also has quality checks in this process. If it looks at the HTML or can reasonably determine from other signals or patterns that a page isn\u2019t good enough quality to index, then it won\u2019t bother sending that to the renderer.<\/p>\n\n\n\n<p>There\u2019s also a quirk with news sites. Google wants to index pages on news sites fast so it can index the pages based on the HTML content first\u2014and come back later to render these&nbsp;pages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. Renderer<\/h3>\n\n\n\n<p>The renderer is where Google renders a page to see what a user sees. This is where it\u2019s going to process the JavaScript and any changes made by JavaScript to the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Document_Object_Model\">DOM<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"1658\" class=\"wp-image-166821\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-4.png\" alt=\"Illustration of how JavaScript can change the DOM\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-4.png 1600w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-4-410x425.png 410w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-4-768x796.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-4-1482x1536.png 1482w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\"><\/figure>\n\n\n\n<p>For this, Google is using a headless Chrome browser that is now \u201cevergreen,\u201d which means it should use the latest Chrome version and support the latest features. Years ago, Google was rendering with Chrome 41, and many features were not supported at that&nbsp;time.<\/p>\n\n\n\n<p>Google has more <a href=\"https:\/\/developers.google.com\/search\/docs\/guides\/fix-search-javascript\">info on the WRS<\/a>, which includes things like denying permissions, being stateless, flattening light DOM and shadow DOM, and more that is worth reading.<\/p>\n\n\n\n<p>Rendering at web-scale may be the eighth wonder of the world. It\u2019s a serious undertaking and takes a tremendous amount of resources. Because of the scale, Google is taking many shortcuts with the rendering process to speed things up.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Cached resources<\/h4>\n\n\n\n<p>Google is relying heavily on caching resources. Pages are cached. Files are cached. Nearly everything is cached before being sent to the renderer. It\u2019s not going out and downloading each resource for every page load, because that would be expensive for it and website owners. Instead, it uses these cached resources to be more efficient.<\/p>\n\n\n\n<p>The exception to that is XHR requests, which the renderer will do in real&nbsp;time.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">There\u2019s no five-second timeout<\/h4>\n\n\n\n<p>A common SEO myth is that Google only waits five seconds to load your page. While it\u2019s always a good idea to make your site faster, this myth doesn\u2019t really make sense with the way Google caches files mentioned above. It\u2019s already loading a page with everything cached in its systems, not making requests for fresh resources.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1800\" height=\"3112\" class=\"wp-image-166822\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3.png\" alt=\"Illustration of how resources from the page and file cache are sent to the WRS for rendering\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3.png 1800w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3-246x425.png 246w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3-768x1328.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3-888x1536.png 888w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-3-1185x2048.png 1185w\" sizes=\"auto, (max-width: 1800px) 100vw, 1800px\"><\/figure>\n\n\n\n<p>If it only waited five seconds, it would miss a lot of content.<\/p>\n\n\n\n<p>The myth likely comes from the testing tools like the URL Inspection tool where resources are fetched live instead of cached, and they need to return a result to users within a reasonable amount of time. It could also come from pages not being prioritized for crawling, which makes people think they\u2019re waiting a long time to render and index&nbsp;them.<\/p>\n\n\n\n<p>There is no fixed timeout for the renderer. It runs with a sped-up timer to see if anything is added at a later time. It also looks at the event loop in the browser to see when all of the actions have been taken. It\u2019s really patient, and you should not be concerned about any specific time&nbsp;limit.<\/p>\n\n\n\n<p>It is patient, but it also has safeguards in place in case something gets stuck or someone is trying to mine Bitcoin on its pages. Yes, it\u2019s a thing. We had to add safeguards for Bitcoin mining as well and even <a href=\"https:\/\/ahrefs.com\/blog\/cryptomining-study\/\">published a study<\/a> about&nbsp;it.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">What Googlebot sees<\/h4>\n\n\n\n<p>Googlebot doesn\u2019t take action on webpages. It\u2019s not going to click things or scroll, but that doesn\u2019t mean it doesn\u2019t have workarounds. As long as content is loaded in the DOM without a needed action, Google will see it. If it\u2019s not loaded into the DOM until after a click, then the content won\u2019t be&nbsp;found.<\/p>\n\n\n\n<p>Google doesn\u2019t need to scroll to see your content either because it has a clever workaround to see the content. For mobile, it loads the page with a screen size of 411x731 pixels and <a href=\"https:\/\/twitter.com\/jroakes\/status\/953604145327624192\">resizes the length to 12,140 pixels<\/a>.&nbsp;<\/p>\n\n\n\n<p>Essentially, it becomes a really long phone with a screen size of 411x12140 pixels. For desktop, it does the same and goes from 1024x768 pixels to 1024x9307 pixels. I haven\u2019t seen any recent tests for these numbers, and it may change depending on how long the pages&nbsp;are.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1800\" height=\"2150\" class=\"wp-image-166824\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5.png\" alt=\"Illustration showing Google doesn't scroll to see content \" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5.png 1800w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5-356x425.png 356w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5-768x917.png 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5-1286x1536.png 1286w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-5-1715x2048.png 1715w\" sizes=\"auto, (max-width: 1800px) 100vw, 1800px\"><\/figure>\n\n\n\n<p>Another interesting shortcut is that Google doesn\u2019t paint the pixels during the rendering process. It takes time and additional resources to finish a page load, and it doesn\u2019t really need to see the final state with the pixels painted. Besides, graphics cards are expensive between gaming, crypto mining, and&nbsp;AI.<\/p>\n\n\n\n<p>Google just needs to know the structure and the layout, and it gets that without having to actually paint the pixels. As <a href=\"https:\/\/twitter.com\/g33konaut\">Martin<\/a> puts&nbsp;it:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-rich is-provider-embed-handler wp-block-embed-embed-handler\">\n<div class=\"wp-block-embed__wrapper\">https:\/\/www.youtube-nocookie.com\/embed\/Qxd_d9m9vzo?start=154<\/div>\n<\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>In Google search we don\u2019t really care about the pixels because we don\u2019t really want to show it to someone. We want to process the information and the semantic information so we need something in the intermediate state. We don\u2019t have to actually paint the pixels.<\/p>\n<\/blockquote>\n\n\n\n<p>A visual may help explain what is cut out a bit better. In Chrome Dev Tools, if you run a test on the \u201cPerformance\u201d tab, you get a loading chart. The solid green part here represents the painting stage. For Googlebot, that never happens, so it saves resources.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1387\" height=\"266\" class=\"wp-image-166826\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image27-2.png\" alt=\"Performance chart from Chrome Dev Tools\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image27-2.png 1387w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image27-2-680x130.png 680w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image27-2-768x147.png 768w\" sizes=\"auto, (max-width: 1387px) 100vw, 1387px\"><\/figure>\n\n\n\n<p><strong>Gray<\/strong> = Downloads<br><strong>Blue<\/strong> = HTML<br><strong>Yellow<\/strong> = JavaScript<br><strong>Purple<\/strong> = Layout<br><strong>Green<\/strong> = Painting<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Crawl&nbsp;queue<\/h3>\n\n\n\n<p>Google has a resource that talks a bit about crawl budget. But you should know that each site has its own crawl budget, and each request has to be prioritized. Google also has to balance crawling your pages vs. every other page on the internet.<\/p>\n\n\n\n<p>Newer sites in general or sites with a lot of dynamic pages will likely be crawled slower. Some pages will be updated less often than others, and some resources may also be requested less frequently.<\/p>\n\n\n\n<div class=\"post-nav-link clearfix\" id=\"section1\"><a class=\"subhead-anchor\" data-tip=\"tooltip__copielink\" rel=\"#section1\"><svg width=\"19\" height=\"19\" viewBox=\"0 0 14 14\" style><g fill=\"none\" fill-rule=\"evenodd\"><path d=\"M0 0h14v14H0z\" \/><path d=\"M7.45 9.887l-1.62 1.621c-.92.92-2.418.92-3.338 0a2.364 2.364 0 0 1 0-3.339l1.62-1.62-1.273-1.272-1.62 1.62a4.161 4.161 0 1 0 5.885 5.884l1.62-1.62L7.45 9.886zM5.527 5.135L7.17 3.492c.92-.92 2.418-.92 3.339 0 .92.92.92 2.418 0 3.339L8.866 8.473l1.272 1.273 1.644-1.643A4.161 4.161 0 1 0 5.897 2.22L4.254 3.863l1.272 1.272zm-.66 3.998a.749.749 0 0 1 0-1.06l2.208-2.206a.749.749 0 1 1 1.06 1.06L5.928 9.133a.75.75 0 0 1-1.061 0z\" style \/><\/g><\/svg><\/a><div class=\"link-text\" data-anchor=\"JavaScript rendering options\" data-section=\"javascript-rendering-options\">&nbsp;\n\n\n\n<h2 class=\"wp-block-heading\">JavaScript rendering options<\/h2>\n\n\n\n<\/div><\/div>\n\n\n\n<p>There are lots of options when it comes to rendering JavaScript. Google has a solid chart that I\u2019m just going to show. Any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines. Gatsby, Next, Nuxt, etc., are all&nbsp;great.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"1061\" class=\"wp-image-166827\" src=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image17-2.jpg\" alt=\"JavaScript rendering options\" srcset=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image17-2.jpg 1600w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image17-2-641x425.jpg 641w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image17-2-768x509.jpg 768w, https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image17-2-1536x1019.jpg 1536w\" sizes=\"auto, (max-width: 1600px) 100vw, 1600px\">\n<figcaption class=\"wp-element-caption\">Source: <a href=\"https:\/\/web.dev\/rendering-on-the-web\/\">web.dev<\/a>.<\/figcaption>\n<\/figure>\n\n\n\n<p>The most problematic one is going to be full client-side rendering where all of the rendering happens in the browser. While Google will probably be OK client-side rendering, it\u2019s best to choose a different rendering option to support other <a href=\"https:\/\/ahrefs.com\/blog\/alternative-search-engines\/\">search engines<\/a>.<\/p>\n\n\n\n<p>Bing also has <a href=\"https:\/\/blogs.bing.com\/webmaster\/october-2019\/The-new-evergreen-Bingbot-simplifying-SEO-by-leveraging-Microsoft-Edge\/\">support for JavaScript rendering<\/a>, but the scale is unknown. Yandex and Baidu have limited support from what I\u2019ve seen, and many other search engines have little to no support for JavaScript. Our own search engine, <a href=\"https:\/\/yep.com\/\">Yep<\/a>, has support, and we render ~200M pages per day. But we don\u2019t render every page we&nbsp;crawl.<\/p>\n\n\n\n<p>There\u2019s also the option of <a href=\"https:\/\/developers.google.com\/search\/docs\/guides\/dynamic-rendering\">dynamic rendering<\/a>, which is rendering for certain user-agents. This is a workaround and, to be honest, I never recommended it and am glad Google is recommending against it now as&nbsp;well.<\/p>\n\n\n\n<p>Situationally, you may want to use it to render for certain bots like search engines or even social media bots. Social media bots don\u2019t run JavaScript, so things like <a href=\"https:\/\/ahrefs.com\/blog\/open-graph-meta-tags\/\">OG tags<\/a> won\u2019t be seen unless you render the content before serving it to&nbsp;them.<\/p>\n\n\n\n<p>Practically, it makes setups more complex and harder for SEOs to troubleshoot. It\u2019s definitely <a href=\"https:\/\/developers.google.com\/search\/docs\/essentials\/spam-policies#cloaking\">cloaking<\/a>, even though Google says it\u2019s not and that it\u2019s OK with&nbsp;it.<\/p>\n\n\n\n<div class=\"recommendation\"><div class=\"recommendation-title\">Note<\/div><div class=\"recommendation-content\">\n\n\n\n<p>If you were using the old <a href=\"https:\/\/developers.google.com\/webmasters\/ajax-crawling\/docs\/learn-more\">AJAX crawling scheme<\/a> with hashbangs (#!), do know this has been deprecated and is no longer supported.<\/p>\n\n\n\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Final thoughts<\/h2>\n\n\n\n<p>JavaScript is not something for SEOs to fear. Hopefully, this article has helped you understand how to work with it better.&nbsp;<\/p>\n\n\n\n<p>Don\u2019t be afraid to reach out to your developers and work with them and ask them questions. They are going to be your greatest allies in helping to improve your JavaScript site for search engines.<\/p>\n\n\n\n<p>Have questions? Let me know <a href=\"https:\/\/twitter.com\/patrickstox\">on Twitter<\/a>.<\/p>\n\n\n\n<div class=\"further-reading\"><div class=\"reading-title\">Further reading<\/div><div class=\"reading-content\">\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/developers.google.com\/search\/docs\/guides\/javascript-seo-basics\">JavaScript SEO Basics<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.youtube.com\/channel\/UCWf2ZlNsCGDS89VBF_awNvA\">JavaScript SEO Office Hours<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/groups.google.com\/forum\/#!forum\/js-sites-wg\">JavaScript Sites Working Group<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.youtube.com\/playlist?list=PLKoqnv2vTMUPOalM1zuWDP9OQl851WMM9\">JavaScript SEO Video Series<\/a><\/li>\n<\/ul>\n\n\n\n<\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>The reality of the current web is that JavaScript is everywhere. Most websites use some kind of JavaScript to add interactivity and improve user experience. Yet most of the JavaScript used on so many websites won\u2019t impact SEO at all.<span class=\"ellipsis\">\u2026<\/span><\/p>\n<div class=\"read-more\">Read more \u203a<\/div>\n<p><!-- end of .read-more --><\/p>\n","protected":false},"author":150,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"wp_typography_post_enhancements_disabled":false,"footnotes":""},"categories":[329],"tags":[],"coauthors":[377],"class_list":["post-166763","post","type-post","status-publish","format-standard","hentry","category-technical-seo","odd"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>JavaScript SEO Issues &amp; Best Practices<\/title>\n<meta name=\"description\" content=\"As JavaScript is found everywhere, SEOs should understand how Google handles it and how to troubleshoot issues.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ahrefs.com\/blog\/javascript-seo\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"JavaScript SEO Issues &amp; Best Practices\" \/>\n<meta property=\"og:description\" content=\"Issues, best practices, and troubleshooting.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ahrefs.com\/blog\/javascript-seo\/\" \/>\n<meta property=\"og:site_name\" content=\"SEO Blog by Ahrefs\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Ahrefs\/\" \/>\n<meta property=\"article:author\" content=\"patrickstox\" \/>\n<meta property=\"article:published_time\" content=\"2023-09-21T07:52:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-09T18:44:43+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png\" \/>\n<meta name=\"author\" content=\"Patrick Stox\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Issues, best practices, and troubleshooting.\" \/>\n<meta name=\"twitter:creator\" content=\"@patrickstox\" \/>\n<meta name=\"twitter:site\" content=\"@ahrefs\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/\"},\"author\":{\"name\":\"Patrick Stox\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#\\\/schema\\\/person\\\/14bf754248f3c561786477e4e5fd2067\"},\"headline\":\"JavaScript SEO Issues &amp; Best Practices\",\"datePublished\":\"2023-09-21T07:52:00+00:00\",\"dateModified\":\"2025-05-09T18:44:43+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/\"},\"wordCount\":6924,\"publisher\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/09\\\/javascript-seo-issues-amp-best-practices-by-patrick-stox-technical-seo.jpg\",\"articleSection\":[\"Technical SEO\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/\",\"name\":\"JavaScript SEO Issues &amp; Best Practices\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/09\\\/image26-2.png\",\"datePublished\":\"2023-09-21T07:52:00+00:00\",\"dateModified\":\"2025-05-09T18:44:43+00:00\",\"description\":\"As JavaScript is found everywhere, SEOs should understand how Google handles it and how to troubleshoot issues.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/javascript-seo\\\/#primaryimage\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/09\\\/image26-2.png\",\"contentUrl\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/09\\\/image26-2.png\",\"width\":1999,\"height\":907},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/\",\"name\":\"SEO Blog by Ahrefs\",\"description\":\"Link Building Strategies &amp; SEO Tips\",\"publisher\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#organization\",\"name\":\"Ahrefs\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/06\\\/ahrefs-logo.png\",\"contentUrl\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/06\\\/ahrefs-logo.png\",\"width\":2048,\"height\":768,\"caption\":\"Ahrefs\"},\"image\":{\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/Ahrefs\\\/\",\"https:\\\/\\\/x.com\\\/ahrefs\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/ahrefs\\\/\",\"https:\\\/\\\/www.youtube.com\\\/c\\\/ahrefscom\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/#\\\/schema\\\/person\\\/14bf754248f3c561786477e4e5fd2067\",\"name\":\"Patrick Stox\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/11\\\/Screenshot-2019-11-06-at-00.57.29.pngbade1fd182f70b6825c334271c12533e\",\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/11\\\/Screenshot-2019-11-06-at-00.57.29.png\",\"contentUrl\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/11\\\/Screenshot-2019-11-06-at-00.57.29.png\",\"caption\":\"Patrick Stox\"},\"description\":\"Patrick Stox is a Product Advisor, Technical SEO, &amp; Brand Ambassador at Ahrefs. He was the lead author for the SEO chapter of the 2021 Web Almanac and a reviewer for the 2022 SEO chapter. He also co-wrote the SEO Book For Beginners by Ahrefs and was the Technical Review Editor for The Art of SEO 4th Edition. He\u2019s an organizer for the Triangle SEO Meetup, the Tech SEO Connect conference, he runs a Technical SEO Slack group, and is a moderator for \\\/r\\\/TechSEO on Reddit.\",\"sameAs\":[\"https:\\\/\\\/patrickstox.com\\\/\",\"patrickstox\",\"https:\\\/\\\/x.com\\\/patrickstox\"],\"url\":\"https:\\\/\\\/ahrefs.com\\\/blog\\\/author\\\/patrick-stox\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"JavaScript SEO Issues &amp; Best Practices","description":"As JavaScript is found everywhere, SEOs should understand how Google handles it and how to troubleshoot issues.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ahrefs.com\/blog\/javascript-seo\/","og_locale":"en_US","og_type":"article","og_title":"JavaScript SEO Issues &amp; Best Practices","og_description":"Issues, best practices, and troubleshooting.","og_url":"https:\/\/ahrefs.com\/blog\/javascript-seo\/","og_site_name":"SEO Blog by Ahrefs","article_publisher":"https:\/\/www.facebook.com\/Ahrefs\/","article_author":"patrickstox","article_published_time":"2023-09-21T07:52:00+00:00","article_modified_time":"2025-05-09T18:44:43+00:00","og_image":[{"url":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png","type":"","width":"","height":""}],"author":"Patrick Stox","twitter_card":"summary_large_image","twitter_description":"Issues, best practices, and troubleshooting.","twitter_creator":"@patrickstox","twitter_site":"@ahrefs","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/#article","isPartOf":{"@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/"},"author":{"name":"Patrick Stox","@id":"https:\/\/ahrefs.com\/blog\/#\/schema\/person\/14bf754248f3c561786477e4e5fd2067"},"headline":"JavaScript SEO Issues &amp; Best Practices","datePublished":"2023-09-21T07:52:00+00:00","dateModified":"2025-05-09T18:44:43+00:00","mainEntityOfPage":{"@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/"},"wordCount":6924,"publisher":{"@id":"https:\/\/ahrefs.com\/blog\/#organization"},"image":{"@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/#primaryimage"},"thumbnailUrl":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/javascript-seo-issues-amp-best-practices-by-patrick-stox-technical-seo.jpg","articleSection":["Technical SEO"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/","url":"https:\/\/ahrefs.com\/blog\/javascript-seo\/","name":"JavaScript SEO Issues &amp; Best Practices","isPartOf":{"@id":"https:\/\/ahrefs.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/#primaryimage"},"image":{"@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/#primaryimage"},"thumbnailUrl":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png","datePublished":"2023-09-21T07:52:00+00:00","dateModified":"2025-05-09T18:44:43+00:00","description":"As JavaScript is found everywhere, SEOs should understand how Google handles it and how to troubleshoot issues.","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ahrefs.com\/blog\/javascript-seo\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ahrefs.com\/blog\/javascript-seo\/#primaryimage","url":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png","contentUrl":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/09\/image26-2.png","width":1999,"height":907},{"@type":"WebSite","@id":"https:\/\/ahrefs.com\/blog\/#website","url":"https:\/\/ahrefs.com\/blog\/","name":"SEO Blog by Ahrefs","description":"Link Building Strategies &amp; SEO Tips","publisher":{"@id":"https:\/\/ahrefs.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ahrefs.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ahrefs.com\/blog\/#organization","name":"Ahrefs","url":"https:\/\/ahrefs.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ahrefs.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/06\/ahrefs-logo.png","contentUrl":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2023\/06\/ahrefs-logo.png","width":2048,"height":768,"caption":"Ahrefs"},"image":{"@id":"https:\/\/ahrefs.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Ahrefs\/","https:\/\/x.com\/ahrefs","https:\/\/www.linkedin.com\/company\/ahrefs\/","https:\/\/www.youtube.com\/c\/ahrefscom"]},{"@type":"Person","@id":"https:\/\/ahrefs.com\/blog\/#\/schema\/person\/14bf754248f3c561786477e4e5fd2067","name":"Patrick Stox","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2019\/11\/Screenshot-2019-11-06-at-00.57.29.pngbade1fd182f70b6825c334271c12533e","url":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2019\/11\/Screenshot-2019-11-06-at-00.57.29.png","contentUrl":"https:\/\/ahrefs.com\/blog\/wp-content\/uploads\/2019\/11\/Screenshot-2019-11-06-at-00.57.29.png","caption":"Patrick Stox"},"description":"Patrick Stox is a Product Advisor, Technical SEO, &amp; Brand Ambassador at Ahrefs. He was the lead author for the SEO chapter of the 2021 Web Almanac and a reviewer for the 2022 SEO chapter. He also co-wrote the SEO Book For Beginners by Ahrefs and was the Technical Review Editor for The Art of SEO 4th Edition. He\u2019s an organizer for the Triangle SEO Meetup, the Tech SEO Connect conference, he runs a Technical SEO Slack group, and is a moderator for \/r\/TechSEO on Reddit.","sameAs":["https:\/\/patrickstox.com\/","patrickstox","https:\/\/x.com\/patrickstox"],"url":"https:\/\/ahrefs.com\/blog\/author\/patrick-stox\/"}]}},"_links":{"self":[{"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/posts\/166763","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/users\/150"}],"replies":[{"embeddable":true,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/comments?post=166763"}],"version-history":[{"count":0,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/posts\/166763\/revisions"}],"wp:attachment":[{"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/media?parent=166763"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/categories?post=166763"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/tags?post=166763"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/ahrefs.com\/blog\/wp-json\/wp\/v2\/coauthors?post=166763"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}