GEO con Angular: Guía completa de implementación
advancedAngular implementa GEO con @angular/ssr obligatorio, Meta service para Open Graph y article dates, Title service para el title tag, y inyección manual de JSON-LD via DOCUMENT token. Sin SSR Angular sirve HTML vacío y es invisible para todos los crawlers de IA. Este es el requisito más crítico.
GEO con Angular: Guía completa de implementación
Angular implementa GEO con @angular/ssr obligatorio, Meta service para Open Graph y article dates, Title service para el title tag, y inyección manual de JSON-LD via token DOCUMENT. Sin SSR Angular sirve HTML vacío y es invisible para todos los crawlers de IA.
Obligatorio:
@angular/ssrinstalado y configurado. Sin SSR, los crawlers de IA reciben HTML vacío y el sitio es invisible para todos los sistemas generativos.
Instalación de Angular SSR
ng add @angular/ssr
Esto configura automáticamente el server.ts y las entradas necesarias para SSR.
Implementación del componente
// guia-geo.component.ts
import { Component, OnInit, inject } from '@angular/core'
import { Meta, Title } from '@angular/platform-browser'
import { DOCUMENT } from '@angular/common'
@Component({
selector: 'app-guia-geo',
templateUrl: './guia-geo.component.html',
})
export class GuiaGeoComponent implements OnInit {
private meta = inject(Meta)
private title = inject(Title)
private doc = inject(DOCUMENT)
private readonly publishedTime = '2026-04-18T00:00:00Z'
private readonly url = 'https://misitio.com/guia-geo'
ngOnInit() {
this.title.setTitle('Cómo implementar GEO en Angular | Mi Sitio')
const tags = [
{ name: 'description', content: 'GEO en Angular con SSR, meta service y JSON-LD. Guía completa.' },
{ name: 'author', content: 'Mi Empresa' },
{ name: 'robots', content: 'index, follow' },
{ property: 'og:type', content: 'article' },
{ property: 'og:title', content: 'Cómo implementar GEO en Angular' },
{ property: 'og:description', content: 'Guía técnica de GEO para Angular' },
{ property: 'og:url', content: this.url },
{ property: 'og:site_name', content: 'Mi Sitio' },
{ property: 'og:image', content: 'https://misitio.com/og/guia-geo.jpg' },
{ property: 'og:locale', content: 'es_ES' },
{ property: 'article:published_time', content: this.publishedTime },
{ property: 'article:modified_time', content: this.publishedTime },
{ property: 'article:author', content: 'https://misitio.com/author/mi-empresa' },
{ property: 'article:section', content: 'Guías técnicas' },
{ property: 'article:tag', content: 'GEO' },
]
tags.forEach(tag => this.meta.updateTag(tag))
// URL canónica
const canonical = this.doc.createElement('link')
canonical.rel = 'canonical'
canonical.href = this.url
this.doc.head.appendChild(canonical)
// JSON-LD
const script = this.doc.createElement('script')
script.type = 'application/ld+json'
script.text = JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Cómo implementar GEO en Angular',
description: 'Guía técnica de GEO para Angular',
author: { '@type': 'Organization', name: 'Mi Empresa' },
publisher: {
'@type': 'Organization',
name: 'Mi Sitio',
logo: { '@type': 'ImageObject', url: 'https://misitio.com/logo.png' },
},
datePublished: this.publishedTime,
dateModified: this.publishedTime,
mainEntityOfPage: { '@type': 'WebPage', '@id': this.url },
})
this.doc.head.appendChild(script)
}
}
Servicio SeoService reutilizable
Para no repetir la lógica en cada componente, crea un servicio centralizado:
// services/seo.service.ts
import { Injectable, inject } from '@angular/core'
import { Meta, Title } from '@angular/platform-browser'
import { DOCUMENT } from '@angular/common'
export interface SeoConfig {
title: string
description: string
url: string
publishedTime: string
modifiedTime: string
author: string
section?: string
image?: string
}
@Injectable({ providedIn: 'root' })
export class SeoService {
private meta = inject(Meta)
private title = inject(Title)
private doc = inject(DOCUMENT)
setPageSeo(config: SeoConfig) {
this.title.setTitle(`${config.title} | Mi Sitio`)
this.meta.updateTag({ name: 'description', content: config.description })
this.meta.updateTag({ name: 'robots', content: 'index, follow' })
this.meta.updateTag({ property: 'og:type', content: 'article' })
this.meta.updateTag({ property: 'og:title', content: config.title })
this.meta.updateTag({ property: 'og:description', content: config.description })
this.meta.updateTag({ property: 'og:url', content: config.url })
this.meta.updateTag({ property: 'article:published_time', content: config.publishedTime })
this.meta.updateTag({ property: 'article:modified_time', content: config.modifiedTime })
this.meta.updateTag({ property: 'article:author', content: config.author })
if (config.image) {
this.meta.updateTag({ property: 'og:image', content: config.image })
}
this.setCanonical(config.url)
this.setJsonLd(config)
}
private setCanonical(url: string) {
let link = this.doc.querySelector('link[rel=canonical]') as HTMLLinkElement
if (!link) {
link = this.doc.createElement('link')
link.rel = 'canonical'
this.doc.head.appendChild(link)
}
link.href = url
}
private setJsonLd(config: SeoConfig) {
const existing = this.doc.querySelector('script[type="application/ld+json"]')
if (existing) existing.remove()
const script = this.doc.createElement('script')
script.type = 'application/ld+json'
script.text = JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: config.title,
description: config.description,
datePublished: config.publishedTime,
dateModified: config.modifiedTime,
author: { '@type': 'Person', name: config.author },
publisher: { '@type': 'Organization', name: 'Mi Sitio' },
mainEntityOfPage: { '@type': 'WebPage', '@id': config.url },
})
this.doc.head.appendChild(script)
}
}
robots.txt
Coloca robots.txt en la carpeta src/ (se sirve como asset estático):
User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-User
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: BingBot
Allow: /
User-agent: *
Allow: /
Sitemap: https://misitio.com/sitemap.xml
Sitemap: https://misitio.com/llms.txt
Asegúrate de incluirlo en assets en angular.json:
{
"assets": [
"src/favicon.ico",
"src/assets",
"src/robots.txt",
"src/llms.txt"
]
}
Checklist GEO para Angular
- @angular/ssr instalado y configurado — obligatorio para GEO
- Title service: setTitle() en ngOnInit de cada componente de página
- Meta service: description, robots, og:type, og:title, og:description, og:url
- Meta service: article:published_time y article:modified_time
- DOCUMENT token: canonical URL añadida al head
- DOCUMENT token: JSON-LD Article schema añadido al head
- SeoService reutilizable (recomendado para proyectos grandes)
- src/robots.txt con los 8 crawlers de IA permitidos
- src/llms.txt con descripción del sitio
- angular.json: robots.txt y llms.txt en assets
- Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1