Por qué el SEO en Angular es diferente
Angular es un framework SPA (Single Page Application). Por defecto, el servidor envia un index.html vacio con un bundle de JavaScript que construye la página en el navegador. Esto presenta dos problemas para SEO:
- Google renderiza JavaScript, pero no siempre de forma completa o rápida. Otros motores de busqueda (Bing, DuckDuckGo) tienen soporte limitado
- Los crawlers de redes sociales (Facebook, Twitter, LinkedIn) no ejecutan JavaScript. Sin SSR, tus Open Graph tags son invisibles
La solucion es Server-Side Rendering (SSR): el servidor genera el HTML completo y lo envia al navegador. Angular 21 tiene SSR integrado y configurarlo es más sencillo que nunca.
Habilitando SSR en Angular 21
Si tu proyecto no tiene SSR, agregalo con:
ng add @angular/ssrEsto genera:
server.ts: El servidor Expresssrc/app/app.config.server.ts: Configuración del servidor- Actualizaciones en
angular.jsonpara build SSR
Verificar que funciona
ng serve
# Abre view-source:http://localhost:4200
# Deberias ver HTML renderizado, no un <app-root> vacioEl SeoService: tu herramienta central
Centraliza toda la lógica de SEO en un servicio. El primer bloque de código muestra la implementación completa.
Cómo usarlo en componentes
@Component({
selector: 'app-blog-post',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<!-- ... -->`,
})
export class BlogPostComponent {
private readonly seo = inject(SeoService);
private readonly route = inject(ActivatedRoute);
constructor() {
effect(() => {
const post = this.post();
if (post) {
this.seo.updateSeo({
title: post.title,
description: post.excerpt,
url: `/blog/${post.slug}`,
image: post.coverImage,
type: 'article',
publishedAt: post.publishedAt,
updatedAt: post.updatedAt,
author: post.author,
tags: post.tags,
});
}
});
}
}Meta tags esenciales
Cada página debe tener como mínimo:
| Tag | Proposito |
|---|---|
<title> |
Título en resultados de busqueda |
meta description |
Descripcion bajo el título |
og:title |
Título en redes sociales |
og:description |
Descripcion en redes sociales |
og:image |
Imagen al compartir (1200x630px) |
og:url |
URL canonica |
canonical |
Evita contenido duplicado |
Structured Data con JSON-LD
Los datos estructurados ayudan a Google a entender el tipo de contenido de tu página y a mostrar rich snippets en los resultados de busqueda.
El segundo bloque de código muestra un servicio que inyecta JSON-LD para articulos y cursos.
Rich snippets que puedes obtener
- Articulos: Fecha, autor, imagen en resultados
- Cursos: Proveedor, nivel, precio en resultados
- FAQ: Preguntas y respuestas expandibles
- Breadcrumb: Navegación jerarquica
- Organization: Logo y datos de la empresa
Breadcrumbs structured data
addBreadcrumb(items: Array<{ name: string; url: string }>): void {
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: `https://xbemorelearn.web.app${item.url}`,
})),
};
this.injectSchema(schema);
}SEO en las rutas
Puedes usar resolvers de Angular Router para actualizar el SEO antes de que el componente cargue. El tercer bloque de código muestra este patrón.
Para rutas estaticas
Usa el resolver directamente con datos fijos:
{
path: 'cursos',
resolve: {
seo: () => {
inject(SeoService).updateSeo({
title: 'Cursos de desarrollo web',
description: 'Cursos gratuitos de Angular, TypeScript, CSS y más.',
url: '/cursos',
});
return true;
},
},
}Para rutas dinamicas
El SEO se actualiza en el componente cuando los datos estan disponibles:
{
path: ':slug',
loadComponent: () => import('./course-detail')
.then(m => m.CourseDetailComponent),
// El componente actualiza el SEO con los datos del curso
}Sitemap y robots.txt
robots.txt
Crea src/robots.txt y agregalo a los assets en angular.json:
User-agent: *
Allow: /
Sitemap: https://xbemorelearn.web.app/sitemap.xmlSitemap estático
Para apps con contenido estático o pre-renderizado, genera un sitemap en build time:
<?xml versión="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://xbemorelearn.web.app/</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://xbemorelearn.web.app/blog</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://xbemorelearn.web.app/cursos</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
</urlset>Sitemap dinámico
Para generar el sitemap automaticamente con cada build, crea un script de prebuild:
import { writeFileSync } from 'fs';
import { BLOG_POSTS } from '../src/app/data/blog/posts.data';
import { ALL_COURSES } from '../src/app/data/courses';
const baseUrl = 'https://xbemorelearn.web.app';
const staticPages = [
{ url: '/', priority: '1.0', changefreq: 'weekly' },
{ url: '/blog', priority: '0.9', changefreq: 'daily' },
{ url: '/cursos', priority: '0.9', changefreq: 'weekly' },
{ url: '/rutas', priority: '0.8', changefreq: 'monthly' },
];
const blogPages = BLOG_POSTS.map(post => ({
url: `/blog/${post.slug}`,
priority: '0.7',
changefreq: 'monthly' as const,
lastmod: post.updatedAt,
}));
const coursePages = ALL_COURSES.map(course => ({
url: `/cursos/${course.slug}`,
priority: '0.8',
changefreq: 'monthly' as const,
}));
const allPages = [...staticPages, ...blogPages, ...coursePages];
const sitemap = `<?xml versión="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allPages.map(page => ` <url>
<loc>${baseUrl}${page.url}</loc>
<changefreq>${page.changefreq}</changefreq>
<priority>${page.priority}</priority>
</url>`).join('\n')}
</urlset>`;
writeFileSync('src/sitemap.xml', sitemap);Pre-rendering de rutas estaticas
Angular puede pre-renderizar rutas en build time, generando archivos HTML estaticos:
// app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{ path: '', renderMode: RenderMode.Prerender },
{ path: 'blog', renderMode: RenderMode.Prerender },
{ path: 'cursos', renderMode: RenderMode.Prerender },
{ path: 'blog/:slug', renderMode: RenderMode.Prerender },
{ path: 'cursos/:slug', renderMode: RenderMode.Prerender },
{ path: '**', renderMode: RenderMode.Server },
];Las rutas pre-renderizadas son HTML estático puro: no necesitan servidor y se sirven instantaneamente desde CDN. Es la mejor opcion para contenido que no cambia frecuentemente.
Performance como factor SEO
Google usa Core Web Vitals como factor de ranking:
Largest Contentful Paint (LCP)
El elemento más grande visible debe cargar en menos de 2.5 segundos:
- Pre-renderiza las páginas críticas
- Optimiza imagenes (WebP/AVIF, lazy loading)
- Minimiza CSS y JS bloqueante
Cumulative Layout Shift (CLS)
El contenido no debe moverse despues de cargar:
- Define dimensiones en imagenes (
widthyheight) - Usa
NgOptimizedImageque maneja esto automaticamente - Reserva espacio para contenido asincrono
Interaction to Next Paint (INP)
Las interacciones deben responder en menos de 200ms:
- Usa OnPush change detection
- Evita computaciones pesadas en el main thread
- Usa signals para actualizaciones granulares
Auditar tu SEO
Herramientas gratuitas
- Google Search Console: Datos reales de como Google ve tu sitio
- Lighthouse: Auditoria técnica integrada en Chrome
- Schema.org Validator: Valida tus datos estructurados
- Open Graph Debugger: Facebook Sharing Debugger
- Twitter Card Validator: Previsualizacion de Twitter Cards
Checklist SEO para cada página
- Title único y descriptivo (50-60 caracteres)
- Meta description única (150-160 caracteres)
- URL limpia y descriptiva
- Open Graph tags completos
- Canonical URL configurada
- Datos estructurados JSON-LD
- Imagenes con alt text
- Heading hierarchy correcta (un solo H1)
- Contenido sobre el fold visible sin JavaScript
- Tiempo de carga menor a 3 segundos
Conclusion
El SEO en Angular con SSR no es magia negra. Es un conjunto de prácticas técnicas bien definidas: meta tags dinámicos, datos estructurados, sitemap, pre-rendering y rendimiento. El SeoService centralizado te da un punto único para gestionar todo esto de forma consistente.
La inversion en SEO tiene un retorno compuesto: cada página bien optimizada atrae trafico organico que crece con el tiempo. Para una plataforma de aprendizaje como Bemore Learn, esto significa más estudiantes descubriendo el contenido sin gastar en publicidad.



Comentarios (0)
Inicia sesión para comentar