Angular SSR for SEO: What Actually Works
How to set up server-side rendering and prerendering in modern Angular so search engines see your content, plus the hydration gotchas that bite you on the first try.
Search engines have gotten better at crawling JavaScript, but "better" is not "reliably." If SEO matters for your site and you're building with Angular, you need server-side rendering. Without it, your <title> and meta tags are empty when the crawler reads the page, and Google decides on its own when (or whether) to come back for the rendered version.
Angular SSR is now built in, well-supported, and not that hard to set up. Here's what to know.
What SSR actually does
On a client-only Angular app, the server sends a near-empty HTML shell. The browser downloads the JavaScript, runs it, and the page renders. Real users see content within a second or two. Crawlers see an empty page until their second-stage rendering pipeline picks it up — which may be hours or days later, and only for some pages.
With SSR, the server runs Angular and sends back fully rendered HTML on the first request. The crawler sees the same content a user sees. Then Angular hydrates the page in the browser, attaching the interactive behavior to the already-rendered DOM.
The result: the page is indexable instantly, and it still feels like an Angular app.
Setting it up
On a new Angular app, opt in at creation:
``bash ng new my-app --ssr ``
On an existing app, add it:
``bash ng add @angular/ssr ``
That's the easy part. The CLI generates the server entry, the Express setup, and the build target. ng build now produces a server bundle and a browser bundle.
The thing you have to actually do
Set <title> and meta tags on every route. Angular gives you the Title and Meta services — use them, or use the route-level data config and a small service that applies them on navigation.
``typescript export const routes: Routes = [ { path: 'about', component: AboutComponent, data: { title: 'About — Ninja Web Design', description: 'What we do and how we work.' } } ]; ``
Then a service listens to NavigationEnd, reads the data, and calls Title.setTitle(...) and Meta.updateTag(...). Without this, every page has the same title and you've wasted the SSR setup.
For Open Graph and Twitter cards, set them the same way. Crawlers for Facebook and X don't run JavaScript at all — they only see the SSR'd HTML.
Prerendering vs full SSR
Two flavors of server-rendering in Angular:
- Prerendering. Build-time. Each route gets rendered once during
ng buildand saved as a static HTML file. Best for marketing sites, blogs, anything where content doesn't change per-request. Fast, cheap, simple to host. - Full SSR. Request-time. Every request renders fresh on the server. Necessary for personalized content, logged-in views, anything per-user.
Most marketing sites should prerender. This site does. Every page is a real HTML file sitting on disk — fast first load, perfect for search engines, and the server doesn't have to run Angular for every request.
Configure prerendering in angular.json:
``json "prerender": { "routes": ["/", "/about", "/blog", "/contact"] } ``
For a blog with dynamic routes, supply a routesFile listing each post slug. The build resolves them all.
Hydration gotchas
A few things tend to bite you the first time:
- Don't touch
windowordocumentat module load. SSR runs on Node, where those don't exist. Wrap browser-only code inisPlatformBrowser(this.platformId)orafterNextRender(() => ...). - Avoid
setTimeoutin constructors. It runs on the server too, and can cause hydration mismatches. - Don't mutate the DOM directly. Angular's hydration expects the SSR'd DOM to match what the client renders. Nodes injected outside Angular's view cause errors.
- Watch the dates.
Date.now()rendered on the server differs from one rendered on the client. Pass it throughTransferStateor render dates only on the client.
Verifying it worked
Three checks:
- View source on a page (Ctrl+U). The HTML should contain the page content, not just
<app-root></app-root>. - Disable JavaScript in the browser and reload. The page should still display.
- Use Google's URL Inspection tool in Search Console on a deployed page. The rendered HTML preview should match what users see.
If all three pass, your SSR is doing its job.
Where this connects to SEO
SSR is a prerequisite, not the whole story. The same content rules apply — useful pages, real titles, internal links, fast loading. For local rankings specifically, the work I described in local SEO for small business applies the same way to an Angular site as a WordPress one.
If you're building an Angular site and want to make sure the SEO setup isn't going to bite you at launch, send me a message. It's much cheaper to get this right before launch than to try to fix it after.
Want help shipping this on your own site?
Free 30-minute consultation. No pressure either way.
