Site Migration Without Losing SEO: A 9-Step Plan
Site migration SEO without traffic loss: URL inventory, 301 redirect map, staging tests, launch checklist, monitoring and the mistakes that cost the most.
Key takeaways
JavaScript SEO means making sure Google can see the content and links of a site that is built in the browser rather than on the server. Google can render JavaScript, but it does so in a separate step after crawling, which adds delays and risks. The practical rule: everything that needs to be indexed (text, headings, links, canonical) should be available as early as possible, ideally directly in the HTML.
Google's documentation describes three phases: crawling, rendering, indexing.
<a href> elements. If the page is allowed and returns a 200 status, rendering follows.Two consequences follow. First, anything that does not appear in the rendered HTML does not exist for Google. Second, anything that depends on rendering reaches the index later than what is already in the HTML. For a blog that publishes daily or a store with changing stock, that delay can matter.
One useful detail: Googlebot uses a continuously updated Chromium, so it supports modern browser APIs. The problem is rarely that "Google doesn't understand JavaScript." It is how the site is written. That is why rendering belongs to technical SEO, together with indexing, speed and status codes.
The model you pick is the decision that most affects search behavior. web.dev has longer explanations of each.
| Model | Who builds the HTML | For SEO | Typical use |
|---|---|---|---|
| Client-side rendering (CSR) | The browser, after scripts load | Risky: the initial HTML is nearly empty | Apps behind a login, admin dashboards |
| Server-side rendering (SSR) | The server, on every request | Good: content is in the HTML | Stores, pages with frequently changing content |
| Static generation (SSG) | At build time, once | Very good and fast | Blogs, brochure sites, documentation |
| Hydration / hybrid | The server sends HTML, then the browser "activates" it | Good, if the HTML holds the real content | Modern apps with public pages |
In short: for pages that must be found on Google, choose SSR or SSG. Pure CSR is fine for areas that have no place in search.
Serving a pre-rendered version only to bots (dynamic rendering) was a popular workaround. Google's documentation calls it a workaround and points to server-side rendering or pre-rendering as the better option, because dynamic rendering adds complexity and the risk that bots and people see different things.
Googlebot discovers URLs from <a> elements with an href attribute. A button or a <span> with onclick is not followed.
<!-- Correct: a crawler can follow the link -->
<a href="/services/audit">Full audit</a>
<!-- Wrong: there is no URL to discover -->
<span onclick="navigate('/services/audit')">Full audit</span>
<!-- Wrong for indexing: the fragment does not identify a separate page -->
<a href="#/services/audit">Full audit</a>For single-page apps, use the History API, not fragments (#). The old AJAX crawling scheme has been deprecated since 2015.
Googlebot generally does not click, scroll or fill in forms the way a visitor does. If important text only appears after clicking "Read more" or after scrolling, it may be missing from the render. For lazy loading, use recommended mechanisms such as IntersectionObserver or the native loading="lazy" attribute, not scroll events that assume a real user.
You can add or change the title, canonical and meta robots with JavaScript, with limits:
noindex, Google may skip rendering, so do not count on removing it with JavaScript;A single-page app often returns 200 for any address while the interface displays "Page not found." Google sees a 200 and may treat the page as a soft 404. Fixes: return a real 404 status from the server for nonexistent URLs, redirect to a page that returns 404, or dynamically add noindex on the error page. Common reasons for exclusion from the index are explained in the guide to pages not indexed.
If robots.txt blocks the JavaScript or CSS files the page needs to display, Google cannot render it correctly. Do not block script directories. Check your file with the robots.txt generator and test with inspection tools.
Googlebot does not keep local storage, session storage or cookies across page loads. If the page depends on them to show content, it will look empty to Google. Do not make indexable content depend on permissions (camera, location): Googlebot declines them.
Every JavaScript SEO audit starts with one comparison: what the server sends versus what results after rendering.
Ctrl+U) and search for a sentence from the main content. If you find it, the content is in the HTML. If not, it appears only after rendering.When a dynamic page does not behave as expected on Google, the table below helps you start from the symptom rather than from assumptions.
| Symptom in Search Console or search | Probable cause | First check |
|---|---|---|
| Page is indexed, but snippets are empty or generic | Text appears only after rendering, or the title is set late | Compare page source with the rendered HTML in URL Inspection |
| "Discovered, currently not indexed" on many new URLs | Internal links appear late or are missing from the HTML | Look for <a href> in the source and in the sitemap |
| "Soft 404" on pages that should exist | Content does not load during rendering, or the app shows an error | Live test and the console errors |
| Nonexistent pages show up as indexed | The app returns 200 for any URL | The real HTTP response for a made-up address |
| Images do not appear in search | Lazy loading implemented only with scroll events | Use loading="lazy" or IntersectionObserver |
A three-step example check on a product or service page: (1) request the URL with a simple client, without JavaScript, and see whether the product name, description and category link are in the response; (2) open the same URL in Search Console's inspection and compare the rendered HTML; (3) change one letter in the address and check that the server returns 404. If any of the three fails, you have found your priority.
For large sites, work by template: do not check a thousand pages, check one page type at a time (home, category, product, article). If the template is right, thousands of pages are right; if it is wrong, thousands of pages share the same problem.
Google accepts JSON-LD injected with JavaScript, but the documentation recommends testing carefully. It is safer to include it in the HTML the server sends. If you use a framework, generate the JSON-LD block during server-side rendering. Examples and validation are in the structured data guide.
Googlebot has a size limit for each fetched file, applied to uncompressed data. Google's Googlebot documentation currently says it crawls the first 2 MB of a supported file type (64 MB for PDFs), and the limit applies separately to each resource referenced in the HTML (JavaScript, CSS). Other Google crawlers may have different limits. Whatever exceeds the limit is not processed. In practice, a huge JavaScript bundle measured in megabytes is a problem for users as well, not only for Google. Split the code, load non-critical parts on demand and use fingerprinted file names, because Googlebot may cache files and keep using an old version.
Googlebot executes JavaScript, but you should not assume every crawler does. For the bots that feed AI search, the providers' bot documentation does not generally promise that they render scripts, and behavior may differ between bots and change over time. If you want your text to be cited there, the safest route is to have it in the initial HTML. For how to allow or block their access, see the article on AI bots and robots.txt.
<div id="root"> and all content fetched through scripts.href or with fragments instead of real addresses.Not every indexing problem comes from scripts. Before you redesign the architecture, rule out the simple causes: blocked pages, a noindex left from testing, a wrong canonical, thin or duplicate content. Also:
JavaScript is not an enemy of SEO, but it demands discipline. Concrete steps:
<a href> with real URLs, no fragments.If you have a dynamic site that does not index the way it should, our technical SEO service starts with exactly these checks. You will find more guides on the blog.
Frequently asked questions
Yes. Googlebot uses an evergreen Chromium and executes JavaScript. Success depends on the implementation: if links are real, URLs are distinct and content shows up without interaction, pages usually index well. Without those conditions, pages can stay empty or undiscovered.
Not as a long-term solution. Google describes it as a workaround, not a long-term solution, because it adds complexity and can make bots and people see different things. Server-side rendering, static generation or hydration are sturdier alternatives.
Use URL Inspection in Search Console and run the live test: compare the rendered HTML and screenshot with what you see in the browser. You can also search Google for an exact sentence from the page. If the text is missing from the rendered HTML, the script did not run fully or was blocked.
Yes. If you block scripts or styles the page needs in order to display, Google cannot render it correctly and may misread the content or layout. Keep rendering resources accessible and use robots.txt for areas with no search value, such as the cart or internal search.
Yes, it helps discover URLs, especially when internal links are generated late. It does not fix rendering problems, though: if the rendered page is empty or returns the wrong status code, a sitemap will not save it. Use it as a safety net, not as a substitute for real links.
Related service
Keep reading
Site migration SEO without traffic loss: URL inventory, 301 redirect map, staging tests, launch checklist, monitoring and the mistakes that cost the most.
Structured data schema in JSON-LD: which types are worth adding, code for an organization, article and product, how to validate and what Google retired.
Core Web Vitals explained: the LCP, INP and CLS thresholds, how much they affect rankings, how to measure them correctly and what to fix first.
Send us your website address and we’ll reply with a free initial analysis and a concrete SEO strategy — no strings attached.