Client-side rendered sites, as originally built, were not fully compatible with SEO. In 2009 Google introduced an AJAX crawling scheme for developers; it was deprecated in 2015 and is long gone.
Modern JS frameworks — React, Angular, Ember, Vue, Aurelia — have mixed support when it comes to crawling. Frameworks that depend entirely on client-side rendering can lose ground in search results, because a lot of web marketing still depends on what crawlers can see server-side.
There are solutions that let crawlers render and extract content from SPAs, but most of them require extra work. Start with Google’s own guidance on JavaScript SEO basics and the Robots meta tag and X-Robots-Tag HTTP header specifications.
Solution 1: pre-render yourself
Render the HTML for your pages in your own pipeline and serve that to bots. Note that not all frameworks provide this; Angular (with Angular Universal / Angular SSR) is the framework with the longest-standing first-party pre-rendering story. The classic 2017 approach was headless rendering with PhantomJS and caching the HTML output to serve to crawlers. The caveats: the output is not guaranteed to match what a real browser produces, and you add a cache-refresh step to your deployment process.
Solution 2: use a prerender service
Use a hosted service such as Prerender.io or SnapSearch, and redirect crawler user-agents to the rendered version of your pages. A third service from 2017, BromBone, no longer serves its site (the Wayback capture is the only working link).
Solution 3: do nothing (the #! fragment era)
It was always vague to claim that Google “now crawls” SPA websites. The old scheme relied on a special meta tag that flagged hash-based URLs for the crawler:
<meta name="fragment" content="!">
That tag made Google request an escaped version of your URL — https://yoursite.com/#/contact became https://yoursite.com/?_escaped_fragment_=/contact. The scheme is dead; Googlebot has rendered JavaScript with an evergreen Chromium since 2019. Cross your fingers that crawlers keep maturing — or pick solution 4. Try explaining either to the marketing department.
Solution 4: hybrid links (progressive enhancement)
Point the href at a static (or pre-rendered) page and intercept the click for actual users:
<a href="/contact.html" onclick="window.location='/#/contact'; return false;">Contact</a>
Provided you have a prerenderer or a prerender service set up. Done right, this is just progressive enhancement: real URLs that work for every crawler, no matter how JavaScript-literate it is.
Reading this in 2026
- Googlebot renders JavaScript (evergreen Chromium) and has since 2019 — but rendering is deferred and slower than raw HTML, so critical content should still be server-rendered where possible.
- The real answers won: SSR/SSG with hydration is the default in React (Next.js), Vue (Nuxt), SvelteKit, and Angular SSR — the 2017 debate about hacky crawler workarounds mostly dissolved into “just server-render”.
- Dynamic rendering is now a workaround, not a strategy: Google’s dynamic rendering doc explicitly labels it a stopgap for JS you can’t otherwise make crawlable, and support for it is limited.
- The JavaScript SEO basics page is the current ground truth for how Google handles client-side rendered content.
Period references, both still live: SEO and JavaScript: New Challenges and SEO for AngularJS and other JavaScript Frameworks — useful as a snapshot of how contested this space was in 2017.