SEO Guide

AngularDart applications are client-side rendered by default, which can make search engine optimization challenging. This guide covers how to make your AngularDart app SEO-friendly.

The Challenge

Single-page applications (SPAs) render all content with JavaScript. Search engine crawlers may not execute JavaScript, so they might not see your content. Solutions include prerendering and proper meta tag management.

Meta Tags with angulardart_seo

The angulardart_seo package provides a service for managing meta tags dynamically from your components.

Installation

Add angulardart_seo to your pubspec.yaml:

dependencies:
  angulardart_seo: '>=1.0.4 <2.0.0'

Basic Usage

Inject the SeoService into your component and use it to set meta tags:

import 'package:angulardart/angulardart.dart';
import 'package:angulardart_seo/angulardart_seo.dart';

@Component(
  selector: 'about-page',
  template: '<h1>About Us</h1><p>...</p>',
  directives: [],
)
class AboutPageComponent implements OnInit {
  final SeoService _seo;

  AboutPageComponent(this._seo);

  @override
  void ngOnInit() {
    _seo.setTitle('About Us - My AngularDart App');
    _seo.setDescription('Learn about our company and team.');
    _seo.setCanonicalUrl('https://example.com/about');
    _seo.setOgTitle('About Us');
    _seo.setOgDescription('Learn about our company and team.');
    _seo.setOgImage('https://example.com/images/about.jpg');
  }
}

Available Methods

Method Description
setTitle(String) Sets the page <title>
setDescription(String) Sets the <meta name="description">
setCanonicalUrl(String) Sets the <link rel="canonical">
setOgTitle(String) Sets Open Graph title
setOgDescription(String) Sets Open Graph description
setOgImage(String) Sets Open Graph image URL
setOgType(String) Sets Open Graph type (default: website)
setTwitterCard(String) Sets Twitter card type
setTwitterTitle(String) Sets Twitter card title
setTwitterDescription(String) Sets Twitter card description
setTwitterImage(String) Sets Twitter card image
setRobots(String) Sets <meta name="robots"> (e.g., index, follow)

Dynamic Meta Tags for Routes

Update meta tags when the route changes:

@Component(
  selector: 'app-component',
  template: '<router-outlet [routes]="routes"></router-outlet>',
  directives: [RouterOutlet],
)
class AppComponent implements OnInit {
  final Router _router;
  final SeoService _seo;

  AppComponent(this._router, this._seo);

  @override
  void ngOnInit() {
    _router.onRouteChanged.listen((route) {
      switch (route.path) {
        case '/':
          _seo.setTitle('Home - My App');
          _seo.setDescription('Welcome to my AngularDart application.');
          break;
        case '/about':
          _seo.setTitle('About - My App');
          _seo.setDescription('Learn more about us.');
          break;
      }
    });
  }
}

Sitemap Generation

A sitemap helps search engines discover all pages on your site.

Manual Sitemap

Create a sitemap.xml in your web/ directory:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/about</loc>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Automated Sitemap

Use a build script to generate the sitemap from your route definitions:

// tool/generate_sitemap.dart
import 'dart:io';

void main() {
  final routes = ['/', '/about', '/blog', '/contact'];
  final buffer = StringBuffer();
  buffer.writeln('<?xml version="1.0" encoding="UTF-8"?>');
  buffer.writeln('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
  for (final route in routes) {
    buffer.writeln('  <url>');
    buffer.writeln('    <loc>https://example.com$route</loc>');
    buffer.writeln('    <changefreq>weekly</changefreq>');
    buffer.writeln('  </url>');
  }
  buffer.writeln('</urlset>');
  File('web/sitemap.xml').writeAsStringSync(buffer.toString());
}

Run with: dart run tool/generate_sitemap.dart

Robots.txt

Create a robots.txt in your web/ directory:

User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml

Structured Data (JSON-LD)

Add structured data to help search engines understand your content:

_seo.setJsonLd("""
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "My Company",
  "url": "https://example.com",
  "logo": "https://example.com/logo.png"
}
""");

SSR + SEO: Server-Aware SeoService

Starting with angulardart_seo 1.3.0, the SeoService is now server-aware. When running on the server (detected via _head == null on the VM), SEO data is stored in a static map and transferred to the client via TransferState.

How It Works

On the browser, SeoService manipulates <title>, <meta>, <link rel="canonical">, Open Graph tags, and Twitter Cards directly in the DOM. On the server, these operations are serialized into TransferState so the data is available during server rendering:

import 'package:angulardart/angulardart.dart';
import 'package:angulardart_seo/angulardart_seo.dart';

@Component(
  selector: 'about-page',
  template: '<h1>About Us</h1>',
)
class AboutPageComponent implements OnInit {
  final SeoService _seo;

  AboutPageComponent(this._seo);

  @override
  void ngOnInit() {
    // On the server, this stores in TransferState for SSR rendering.
    // On the browser, this sets <title> directly.
    _seo.setTitle('About Us - My AngularDart App');
    _seo.setDescription('Learn about our company and team.');

    // Open Graph tags — also transferred via TransferState on server
    _seo.setOgTitle('About Us');
    _seo.setOgDescription('Learn about our company and team.');
    _seo.setOgImage('https://example.com/images/about.jpg');

    // Twitter Card tags
    _seo.setTwitterCard('summary_large_image');
    _seo.setTwitterTitle('About Us');
    _seo.setTwitterDescription('Learn about our company and team.');

    // Canonical URL
    _seo.setCanonicalUrl('https://example.com/about');

    // JSON-LD structured data
    _seo.setJsonLd('{\n'
        '  "@context": "https://schema.org",\n'
        '  "@type": "Organization",\n'
        '  "name": "My Company"\n'
        '}');
  }
}

Adding SEO to an Existing Project

Use the ngdart add seo command:

cd my_existing_app
ngdart add seo
dart pub get

This adds angulardart_seo >=1.3.0 <2.0.0 to your dependencies and configures everything needed for SEO optimization with SSR support.

What Gets Transferred on the Server

Method TransferState Key HTML Output (SSR)
setTitle(title) 'seo:title' <title>...</title> + meta
setMeta(name, content) 'seo:meta:$name' <meta name="..." content="...">
setOgTag(property, content) 'seo:og:$property' <meta property="og:..." content="...">
setTwitterTag(name, content) 'seo:twitter:$name' <meta name="twitter:...">
setCanonical(url) 'seo:canonical' <link rel="canonical" href="...">
setRobots(robots) / setGooglebot(robots) 'seo:meta:robots' <meta name="robots" content="...">
setJsonLd(id, data) 'seo:jsonld:$id' <script type="application/ld+json">

Best Practices for SSR + SEO

  1. Set SEO tags in ngOnInit() — this runs on both server and client, ensuring tags are set during server rendering
  2. Use unique TransferState keys — prefix with your app name to avoid collisions: 'myapp:seo:title'
  3. Combine with RenderMode.server — for SEO-critical pages (home, about, product listings), use renderMode: RenderMode.server so the server renders the full HTML with all meta tags
  4. Verify in browser dev tools — after hydration, check that <head> contains all expected meta tags

Combining with Prerendering

For best SEO results, combine meta tag management with prerendering:

  1. Use angulardart_seo to set meta tags per route
  2. Use angulardart_prerender to generate static HTML with those tags
  3. Serve prerendered HTML to search engine bots

See Also