prebaked.
Guide

Angular SEO

Last reviewed

"Angular ships all applications as client-side rendered (CSR) by default", so the HTML a crawler receives is an empty <app-root> until JavaScript runs, and the major AI crawlers do not run it. Angular's own fix is @angular/ssr, which adds server rendering and build-time prerendering per route. For an existing app where that migration is not worth it, a prerender service gives crawlers the same finished HTML.

At a glance

Approach Fixes content for AI crawlers Fixes titles and meta tags Effort Best for
Client-side rendering (the default) No Only for crawlers that run JavaScript None Apps behind a login
Angular SSR (RenderMode.Server) Yes Yes Medium to large: a Node.js server, browser-only code guarded New apps, apps with live data you can host on a server
Angular prerender (RenderMode.Prerender, SSG) Yes, for routes known at build time Yes Medium: @angular/ssr, routes and params at build Marketing pages, docs, content that changes on deploy
Title and Meta services alone No Only for crawlers that run JavaScript Low Managing tags together with one of the other options
Prerender service (Prebaked) Yes Yes Low, one snippet Existing CSR apps, older Angular versions, code that breaks on the server

Is Angular good for SEO?

Angular itself is neutral. What matters is how the HTML is produced.

  • Client-side rendering is Angular's default. The server sends index.html with an <app-root> element and scripts. Headings, text and links appear after the app boots.
  • Google renders JavaScript. It queues pages with a 200 status for rendering, and a page "may stay on this queue for a few seconds, but it can take longer than that." Vercel and MERJ measured a median delay of 10 seconds and a 99th percentile of about 18 hours on nextjs.org in April 2024.
  • AI crawlers do not render. Vercel and MERJ found that "none of the major AI crawlers currently render JavaScript", covering OpenAI, Anthropic, Meta, ByteDance and Perplexity crawlers.

So a client-rendered Angular app can rank on Google and still look empty to ChatGPT, Claude and Perplexity. Google itself says "server-side or pre-rendering is still a great idea". More on how AI crawlers see your site.

How to check your Angular app

  1. Run the free checker. It compares your page without JavaScript and in a real browser.

See what ChatGPT sees on your site

  1. Fetch the raw HTML of a deep route.
curl -s https://www.<your-domain>/products | grep -o "<app-root[^>]*>.\{0,80\}"
curl -s https://www.<your-domain>/products | grep -o "<title>.*</title>"

If <app-root> is followed directly by </app-root>, or every route shows the title from index.html, crawlers that do not run JavaScript see an empty page.

  1. For Google, use the URL Inspection tool in Search Console to see the rendered HTML.

Also check the Angular basics: use routerLink on <a> elements so links have a real href, keep the default path-based routing instead of hash URLs (Google says to "use the History API" and not fragments), and make the wildcard ** route return a 404 or add noindex, not a 200 error page.

Angular SSR and prerendering (@angular/ssr)

Angular Universal is now @angular/ssr

Older guides talk about Angular Universal. Its repository says: "In version 17, Universal has been moved into the Angular CLI repo", and the code is "mostly under @angular/ssr now". The repository was archived in November 2024. For current Angular, follow the SSR guide on angular.dev.

Three rendering modes

Angular's SSR guide lists three modes, set per route:

Mode Angular's description
Server (SSR) "Renders the application on the server for each request, sending a fully populated HTML page to the browser."
Client (CSR) Renders in the browser. The default.
Prerender (SSG) "Prerenders the application at build time, generating static HTML files for each route."

Add SSR to an app

# new project
ng new --ssr

# existing project
ng add @angular/ssr

Choose a mode per route

Routes get a render mode in a ServerRoute array, usually in app.routes.server.ts:

import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  { path: '', renderMode: RenderMode.Prerender },
  { path: 'about', renderMode: RenderMode.Prerender },
  { path: 'account', renderMode: RenderMode.Client },
  { path: '**', renderMode: RenderMode.Server },
];

For prerendered routes with parameters, such as products/:id, getPrerenderParams returns the parameter values to build. Routes not listed there fall back to server rendering by default, or to client rendering or none if you choose. When SSR is set up, "Angular prerenders your entire application and generates a server file" by default. Setting outputMode to static in angular.json gives a fully static build without a server.

What it costs

  • A server for SSR routes. Server rendering needs a Node.js server or another platform that runs @angular/ssr.
  • Browser-only code. The guide says applications "cannot make use of browser-specific global objects like window, document, navigator, or location" on the server. That code has to run in afterNextRender or afterEveryRender, which "are only executed on the browser and skipped on the server."
  • Hydration. Content that differs between server and browser "causes hydration mismatches and layout shifts."
  • Prerender only covers what you list. Pages from live data are missing or stale until the next build, unless those routes use SSR.

Choose Angular SSR or prerender when you are starting a new app, you are on a current Angular version, and your code and libraries already run on the server or can be changed to.

Titles and meta tags in Angular

Angular has a Title service and a Meta service ("A service for managing HTML <meta> tags", with addTag and updateTag) in @angular/platform-browser.

import { Component, inject } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';

@Component({ selector: 'app-pricing', template: '<h1>Pricing</h1>' })
export class PricingComponent {
  constructor() {
    inject(Title).setTitle('Pricing | Example');
    inject(Meta).updateTag({ name: 'description', content: 'Plans and prices.' });
  }
}

In a client-rendered app these tags are written by JavaScript, so only crawlers that render see them. With SSR, prerendering or a prerender service, they are in the HTML every crawler receives.

When a prerender service makes sense for Angular

A prerender service renders your pages in a real browser when a crawler asks and serves the HTML from a cache. People still get your normal Angular app. Google calls this dynamic rendering, "a workaround and not a long-term solution", and says that "Googlebot generally doesn't consider dynamic rendering as cloaking." The full explanation is on what is prerendering.

It fits Angular apps when:

  • the app is client-rendered and works, and moving it to @angular/ssr is not planned soon,
  • the Angular version is older and an upgrade to a version with the current SSR APIs is a project of its own,
  • code or libraries depend on window or document in many places, so making them server-safe takes time,
  • pages come from live data and change between deploys, so build-time prerender would be out of date,
  • hosting is static (a CDN or object storage) and you do not want to run a Node.js server.

If you are starting a new Angular app, use @angular/ssr from the start. A prerender service is for the apps that already exist.

When Prebaked is the right choice

  • One snippet: Express with prerender-node if you serve the app from Node, nginx or Cloudflare Worker for static hosting, or another integration.
  • Same page for bots and people: Prebaked renders your page in Chromium and does not change its content.
  • Never worse than today: if Prebaked is slow or down, crawlers get your original page.
  • Status codes kept: a page that renders as a 404 returns 404 to the crawler. Your wildcard route can set it with <meta name="prerender-status-code" content="404">.
  • Ready signal: set window.prerenderReady = true when the data for a page has loaded, so rendering does not stop early.
  • Cache hits are free. You pay for fresh renders only. Warm the cache from your sitemap.

FAQ

Is Angular bad for SEO?

No. Client-side rendering, Angular's default, is the issue. Google renders JavaScript, but AI crawlers do not, so a client-rendered Angular app can look empty to them. Angular SSR, prerendering or a prerender service fixes that.

Is Angular Universal still used?

Not under that name. Universal moved into the Angular CLI in version 17 and is now @angular/ssr. The old repository was archived in November 2024.

What is the difference between Angular SSR and Angular prerender?

SSR renders a route on the server for each request. Prerender renders it once at build time and saves static HTML. In @angular/ssr you choose per route with RenderMode.Server or RenderMode.Prerender.

Do I need SSR for Angular SEO?

Not necessarily. Prerendering covers routes known at build time without a server. For an existing client-rendered app, a prerender service gives crawlers finished HTML without changing the app.

Do the Title and Meta services fix Angular SEO?

Only for crawlers that run JavaScript, unless the page is also server rendered or prerendered. Crawlers that read only the raw HTML see the tags from index.html.