Angular SEO: pick a render mode per route, put titles and meta in the server HTML, and return real status codes
How to make an Angular app readable to search engines with @angular/ssr, route-level render modes, the Title and Meta services, status codes and hydration, from the Angular and Google docs.
Published by AI Knows Us · Updated 21 October 2026
Angular SEO comes down to one decision and four follow-ups. The decision: stop serving search engines an empty shell. Angular ships every app as client-side rendered by default, so add server-side rendering with ng add @angular/ssr and choose, route by route, whether each page is prerendered at build time, rendered on the server per request, or left to the browser. The follow-ups: give every route its own title and meta description, return real HTTP status codes, use real links Google can follow, and turn on hydration so the server HTML is reused instead of thrown away. Everything below comes from Angular's own documentation at angular.dev and Google Search Central, both read on 21 October 2026.
Why a default Angular app is hard for search
Angular's server rendering guide says it plainly: "Angular ships all applications as client-side rendered (CSR) by default." The server sends a small HTML file and a bundle of JavaScript, and the browser builds the page. The same guide warns that client-side rendering "may negatively affect search engine optimization (SEO), as search crawlers have limits to how much JavaScript they execute when indexing a page."
Google processes JavaScript pages in three phases: crawling, rendering and indexing. A page waits in a queue for rendering, and Google's JavaScript SEO guide notes that "it is not immediately obvious when a page is waiting for crawling and when it is waiting for rendering." Google still recommends server-side or pre-rendering, "because it makes your website faster for users and crawlers, and not all bots can run JavaScript."
Step 1: add server rendering and pick a mode per route
For a new project, run ng new --ssr. For an existing one, run ng add @angular/ssr. Angular's docs note that by default it then prerenders the entire application and generates a server file. You control the rest in a server routes file, usually app.routes.server.ts, where each route gets one of three render modes.
| Render mode | What the crawler receives | Use it for |
|---|---|---|
RenderMode.Prerender (SSG) | A static HTML file built at deploy time | Home, about, pricing, docs, blog posts: pages that are the same for every visitor |
RenderMode.Server (SSR) | Fully rendered HTML built for each request | Product and listing pages whose data changes often |
RenderMode.Client (CSR) | The shell, with content built in the browser | Logged-in dashboards and account pages you do not want indexed anyway |
Angular's docs say both server rendering and prerendering "generally" have "excellent search engine optimization (SEO), as search crawlers receive a fully rendered HTML document." A typical config looks like this:
import { RenderMode, ServerRoute } from '@angular/ssr'; export const serverRoutes: ServerRoute[] = [ { path: '', renderMode: RenderMode.Prerender }, { path: 'pricing', renderMode: RenderMode.Prerender }, { path: 'products/:id', renderMode: RenderMode.Server }, { path: 'account/**', renderMode: RenderMode.Client }, { path: '**', renderMode: RenderMode.Server },
];
For prerendered routes with parameters, such as post/:id, add a getPrerenderParams function that returns the list of IDs to build. It runs at build time only. If a visitor requests an ID that was not built, the fallback option decides what happens, for example falling back to client rendering. Every ID you want indexed belongs in that list.
Step 2: give every route its own title and description
Google's guide asks for "unique, descriptive <title> elements and meta descriptions." Angular gives you three tools.
- Route titles. Add a
titleto each route and Angular updates the page title when the route activates. The title can also come from a resolver function, which suits product pages that need data first. - TitleStrategy. If you want a house pattern, such as "Product name | Brand", extend
TitleStrategyonce instead of repeating the suffix in every route. - The Meta service. Angular's
Metaservice from@angular/platform-browseradds, updates and removes<meta>tags. Its own description lists Search Engine Optimization among the uses.
import { Component, inject } from '@angular/core';
import { Meta } from '@angular/platform-browser'; @Component({ selector: 'app-pricing', template: '...' })
export class Pricing { private meta = inject(Meta); constructor() { this.meta.updateTag({ name: 'description', content: 'Plans, limits and what each tier includes.' }); }
}
Because the page is now rendered on the server, these tags are in the HTML the crawler receives, not added later in the browser. For the canonical link, Angular's guide shows a service that injects the DOCUMENT token and appends a <link rel="canonical"> during server rendering. Use DOCUMENT rather than the browser's global document, which does not exist on the server.
Step 3: return real status codes, and know how redirects behave
A single-page app that shows "Product not found" with a 200 status is a soft 404 in Google's eyes. Google notes that pages with a non-200 status, such as 404, may skip rendering entirely, which is what you want for a missing page.
- Fixed codes per route. A
ServerRouteacceptsstatusandheadersproperties, so your not-found route can always return 404. - Codes decided at runtime. The
RESPONSE_INITtoken from@angular/corelets a component set the status code during server rendering, for example after an API says the product does not exist. - Redirects. Angular's docs say
redirectTobecomes a standard HTTP redirect (301 or 302) under server rendering, but a "soft redirect" using<meta http-equiv="refresh">in prerendered HTML. If a prerendered URL has moved for good, set the permanent redirect at your server or CDN as well.
For routes that stay client rendered, Google suggests two options: redirect with JavaScript to a URL the server answers with 404, or add a noindex robots meta tag to the error view.
Step 4: make links that Google can follow
Google "can only discover your links if they are <a> HTML elements with an href attribute." Put Angular's router links on anchor elements, not on buttons or divs with click handlers. Google also says not to use URL fragments, such as /#/products, to load different content, and to use the History API instead. Moving an older app off hash URLs is a high-value fix.
Step 5: turn on hydration
Hydration lets the browser reuse the server-rendered DOM instead of rebuilding it. Angular's hydration guide warns that without it, server-rendered apps "will destroy and re-render the application's DOM, which may result in a visible UI flicker," which can hurt Largest Contentful Paint and cause layout shift. Projects set up with the CLI usually have it already. In a custom setup, add provideClientHydration() to the bootstrap providers.
One trap the guide calls out: do not use isPlatformBrowser inside templates to show different content on server and client. It causes hydration mismatches and layout shifts. Run browser-only code in afterNextRender instead, and keep the rendered content the same on both sides.
What about dynamic rendering and prerender services?
Google describes dynamic rendering as "a workaround and not a recommended solution, because it creates additional complexities and resource requirements." Angular's built-in hybrid rendering removes the need for it. For the wider picture of how Google renders JavaScript frameworks, see our guide to JavaScript SEO.
How to check the work
- View the page source (not the inspector) on a key route. The main heading, body text, title and meta description should be in the raw HTML.
- Run the URL in Google's URL Inspection tool or the Rich Results Test and read the rendered HTML, as Google's guide suggests.
- Request a URL that should not exist and confirm the response code is 404, not 200.
- Check the version. Angular's release page lists v22 as active since 3 June 2026 and says versions 2 to 19 are no longer supported, so very old apps may need an upgrade before the newer server routing APIs are available.
Questions about Angular SEO
Is Angular bad for SEO?
Not when it is set up for it. A default client-rendered Angular app makes crawlers do the rendering. With server rendering or prerendering, crawlers get complete HTML, and Angular's own docs describe that as excellent for SEO.
Should I use prerendering or server-side rendering?
Prerender pages that look the same for everyone and change only when you deploy. Use server rendering for pages whose data changes between deploys, such as stock or prices. You can mix both in one app, route by route.
How do I set the page title in Angular?
Add a title property to each route, or a resolver when the title depends on data. For a site-wide pattern, provide a custom TitleStrategy. Use the Meta service for descriptions and social tags.
Do I still need Angular Universal?
Server rendering now lives in the @angular/ssr package, added with ng add @angular/ssr. Follow the current server and hybrid rendering guide on angular.dev rather than older Universal tutorials.
Why does Google index my Angular page with no content?
Usually Google indexed the client-rendered shell, or an API call failed during rendering. Check the rendered HTML in URL Inspection, then move the route to prerender or server mode.
The same HTML now answers assistants too
Google's own guide says not all bots can run JavaScript, and the crawlers that feed AI assistants are bots too. A product team that moves its Angular marketing pages to server rendering hands every reader, human or machine, the same complete page. What an assistant then says when a buyer asks it to compare products in your category is a separate result you can measure, and AI Knows Us was built to read exactly that.