Technical SEO for Static and JavaScript Sites
A practical guide to technical SEO for JavaScript sites, covering rendering, crawlability, Core Web Vitals, and how to make SPAs visible to search.
On this page
- Rendering is the question everything else depends on
- The three rendering approaches and what they cost you
- Why a pure client-side SPA is the hard case, and how to fix it
- The fundamentals that matter regardless of stack
- Core Web Vitals and speed are ranking signals, not vanity metrics
- Make your URLs real, crawlable, and shareable
- Indexing management for large and programmatic page sets
- Test what Google actually sees, not what you see
- The mistakes I see over and over
- The short version
I have watched more than one beautiful site launch to complete search silence. The product looks fast, the animations are smooth, the team is proud, and then three months pass and organic traffic is a flat line at zero. Nobody broke the content. Nobody forgot to publish. The problem is quieter than that: the pages render perfectly for a human with a browser and a fast connection, and render as an empty shell for the one visitor that decides whether anyone else ever shows up, the search crawler.
That gap between what a person sees and what a crawler sees is the whole of technical SEO for modern sites. When people ask me about technical SEO for JavaScript, they usually expect a checklist of tags. Tags matter, but they come second. The first question, the one that decides whether every other effort pays off, is a rendering question. Can a search engine actually read your content, and can it do so reliably and quickly, or does your content only exist after a pile of JavaScript executes on a machine you do not control?
This post walks through how I think about that question when I build content-led growth on modern stacks. This portfolio itself is a statically generated site, which is not an accident. I want to explain why that choice keeps coming up, what the trade-offs really are, and the fundamentals that still matter no matter which framework you picked.
Rendering is the question everything else depends on
Search engines do two jobs that people tend to blur together. First they crawl, which means fetching a URL and reading whatever HTML comes back. Then, for JavaScript-heavy pages, they may render, which means running the page in a headless browser to see what the script produces. Crawling is cheap and fast. Rendering is expensive, and search engines ration it.
Here is the trap. If your content is present in the HTML response, the crawler sees it immediately, on the first cheap pass. If your content only appears after JavaScript runs, the crawler has to queue your page for the expensive rendering pass, which can happen days later, sometimes not at all for lower-priority pages, and sometimes with a timeout that clips your content halfway. You are asking the most important reader on the internet to do the most work, and you are hoping it feels generous that day.
So the practical framing I use is blunt. Content that matters for search should be in the initial HTML. Everything downstream, your tags, your structured data, your links, only counts if the crawler got to the content in the first place. Get rendering right and the rest of technical SEO becomes housekeeping. Get it wrong and the rest is decoration on a page nobody indexed.
The three rendering approaches and what they cost you
There are three main ways a page reaches a browser, and each has a different SEO profile.
Client-side rendering, the classic single page application, ships a near-empty HTML file plus a bundle of JavaScript. The browser downloads the script, runs it, fetches data, and builds the page in place. For a logged-in app dashboard this is a fine architecture. For public content you want indexed, it is the riskiest choice, because your content does not exist until code executes, and it depends on the crawler choosing to run that code and waiting long enough for it to finish.
Server-side rendering builds the full HTML on the server for each request, then sends it down complete. The crawler gets real content on the first pass. The browser then hydrates the page so it becomes interactive. This is a strong option for content that changes often or is personalized, though it puts real work on your servers and adds latency you have to manage.
Static site generation pre-builds every page into finished HTML at build time. When a request arrives, you serve a file. For content SEO this is the best case I know of. The crawler gets complete HTML instantly, there is no per-request rendering cost, and a static file over a CDN is about as fast as the web gets. The catch is that content updates require a rebuild, so it fits editorial and marketing pages far better than a live feed of user data.
Most real sites end up mixing these. A marketing and content layer that is static or server-rendered, an app layer that is client-rendered behind a login. That split is healthy. The mistake is letting the app-style client rendering leak onto the pages you actually want to rank.
Why a pure client-side SPA is the hard case, and how to fix it
A pure client-side SPA is the hardest thing to rank because it inverts the deal. It says to the crawler: here is an empty page and a set of instructions; go build it yourself. Some crawlers can. Many crawl budgets will not. And AI answer engines, which increasingly send qualified traffic, are even less patient with pages that demand execution before content appears. If that channel matters to you, I have written more on optimizing for AI search, and the rendering rule there is the same, only stricter.
The good news is you rarely have to abandon your framework to fix this. The fix is to make sure real HTML reaches the crawler by one of these routes:
Move the public pages to server-side rendering, so each request returns finished HTML. Nearly every modern framework supports this as a first-class mode now.
Pre-render the public pages with static site generation at build time, which is my default for content and marketing pages.
Add a prerendering step in front of a client-side app, where a service detects crawlers and serves them a pre-rendered HTML snapshot while human users still get the SPA. This is a reasonable bridge for an existing app you cannot rewrite, though it adds a moving part you have to keep honest.
Lean on hydration properly, meaning the server sends complete HTML and the client enhances it, rather than the client building everything from nothing.
The point is not the label. The point is the outcome: when a crawler fetches your important URLs, real content comes back in the response body, without waiting on the client to execute anything.
The fundamentals that matter regardless of stack
Once content reaches the crawler, a set of durable fundamentals decides how well it gets understood and indexed. None of these are new, and all of them still matter.
Crawlability and clean site structure come first. A crawler should be able to reach every important page by following links, with a shallow, logical hierarchy rather than a maze. This is where a deliberate content architecture pays off; the way I organize topics into content clusters is as much a crawl-path decision as an editorial one, because clear internal linking tells search engines what your pages are and how they relate.
XML sitemaps give crawlers an explicit list of the URLs you care about, which matters most for large sites where link discovery alone is slow. Keep the sitemap accurate, drop dead URLs, and submit it in your search console.
Robots directives, both the robots.txt file and meta robots tags, control what gets crawled and indexed. The most common self-inflicted wound I see is a blanket disallow left over from a staging environment, quietly blocking the whole site in production.
Canonical tags tell search engines which URL is the real one when several show the same content. Modern frameworks love to generate near-duplicate URLs through query parameters and routing quirks, so canonicals keep your ranking signals from splitting across copies.
Proper status codes are non-negotiable. A missing page should return 404 or 410, a moved page a 301. SPAs are notorious for returning 200 on everything, including pages that no longer exist, which creates soft 404s that waste crawl budget and confuse indexing.
Meta and structured data give search engines the summary and the machine-readable facts. Titles and meta descriptions still frame your search snippet. Structured data using schema.org, expressed as JSON-LD, lets you declare what a page is, an article, a product, a person, so it becomes eligible for richer results.
Semantic HTML and a sane heading structure round it out. One clear h1, headings that nest in order, real anchor and button elements instead of clickable divs. This helps crawlers and assistive technology alike, and it costs nothing once it is a habit.
Core Web Vitals and speed are ranking signals, not vanity metrics
Speed is not a nicety here. Core Web Vitals, Google’s measures of loading, interactivity, and visual stability, are confirmed ranking inputs, and more importantly they gate whether users stay long enough to convert. JavaScript-heavy sites tend to hurt themselves on exactly these metrics, because a giant bundle blocks the main thread, delays interactivity, and shifts layout around as content pops in late.
The wins are usually unglamorous. Ship less JavaScript. Split bundles so a page loads only what it needs. Defer non-critical scripts. And attack the largest content element on the page, which on most sites is an image or a hero media block. Heavy, unoptimized media is the quiet killer of load times, which is why I treat image and media optimization as a core technical-SEO task rather than a design detail. Right-sized, modern-format, lazily loaded images do more for real-world performance than most framework tweaks.
If you run marketing pages, this connects directly to conversion. Every technical delay is a tax on the exact behavior you built the page to produce, which is why I fold speed work into landing page optimization rather than treating it as a separate engineering chore.
Make your URLs real, crawlable, and shareable
Routing is where a lot of JavaScript sites quietly sabotage themselves. In the early SPA era, apps used hash-based routes, the kind with a fragment after a hash mark, to fake navigation without a server round trip. The problem is that everything after that fragment is, historically, invisible to servers and unreliable as a distinct indexable URL. You end up with one address in the crawler’s eyes and a dozen views in the user’s.
Use the History API so every view has a real, clean path that returns real content when requested directly. A good URL is one you can paste into a fresh browser tab, or a crawler can fetch cold, and get the right page back. That means the server or your static build has to know how to answer each path, not just the homepage. Real URLs are also what make your pages shareable, linkable, and eligible to accumulate the ranking signals that hash fragments throw away.
Indexing management for large and programmatic page sets
Small sites rarely worry about crawl budget. Large ones must. When you generate hundreds or thousands of pages from data, which is the heart of any programmatic SEO effort, you are now managing an inventory, not a handful of documents. I once worked on a system north of two hundred pages, and at that scale the discipline shifts from writing pages to governing them.
The questions become: which of these pages deserve to be indexed, and which are thin duplicates that dilute your quality signals? Are you pointing canonicals correctly across near-identical templates? Is your sitemap segmented so you can see what is and is not getting indexed? Are low-value filter and pagination URLs eating crawl budget that should go to your money pages? At volume, a noindex tag on the right template is as valuable as a new page, because it concentrates authority where it counts instead of spreading it thin.
Test what Google actually sees, not what you see
The single habit that separates people who fix these problems from people who guess at them is testing the rendered output directly, rather than trusting the browser on your own screen. Your browser runs your JavaScript happily on a fast machine. That tells you almost nothing about what a crawler received.
Use your search console’s URL inspection to see the fetched and rendered HTML for a specific page, and confirm your actual content is present in it. Fetch the raw HTML of a URL and read the response body; if your headline and copy are not in there, neither framework docs nor good intentions will save you. For larger sites, server log files are the ground truth, showing which URLs crawlers actually hit, how often, and what status codes they got back. Logs will tell you when crawl budget is being wasted long before rankings move.
Two views, side by side, settle almost every argument: what a human sees in a normal browser, and what comes back in the raw server response. When those two match on the content that matters, you are in good shape. When they diverge, you have found your problem.
The mistakes I see over and over
Most technical-SEO failures on JavaScript sites are a small set of the same errors. Content locked behind JavaScript with no HTML fallback, so the crawler gets an empty shell. Blocking your own scripts and stylesheets in robots.txt, so even a crawler willing to render cannot assemble the page. Hydration so slow the page is unusable, and unrankable, for seconds after it appears. Missing or stale sitemaps and absent canonical tags on templated pages. Hash-based routing that hides your real content behind fragments. And ignoring Core Web Vitals until a ranking drop forces the conversation.
Every one of these is fixable, and none requires abandoning modern tooling. They require deciding, early, that the crawler is a real user with real constraints, and building for it on purpose rather than hoping it keeps up.
The short version
- Rendering is the core technical-SEO decision for modern sites: content that matters must be in the HTML the crawler receives, not built later by JavaScript.
- Static site generation is my default for content SEO because pages ship as finished HTML and load fast; server-side rendering is the strong alternative for frequently changing pages.
- A pure client-side SPA is the hardest case; fix it with SSR, SSG, prerendering, or proper hydration so crawlers get real HTML.
- The fundamentals still apply everywhere: crawlability, clean structure, sitemaps, robots directives, canonicals, correct status codes, meta tags, JSON-LD structured data, and semantic HTML.
- Core Web Vitals are ranking signals; ship less JavaScript and optimize media, because heavy assets are the usual cause of slow pages.
- Use real, path-based URLs, not hash routes, so pages are crawlable and shareable.
- At scale, manage indexing deliberately: canonicals, segmented sitemaps, and noindex on thin templates protect your crawl budget.
- Test what search engines actually see with URL inspection, raw HTML checks, and log files, rather than trusting your own browser.
I am Deepanshu Grover, a Growth Product Manager in Paris. If your JavaScript site looks great to users but invisible to Google, connect on LinkedIn or get in touch.
Deepanshu Grover
Growth Product Manager in Paris. I find the broken or underused lever in a business and rebuild it into a growth channel.