Skip to content

SEO

The framework provides a central SEO component powered by react-helmet-async. When used inside pages, it sets meta tags and optional JSON-LD, and the server injects this content into the HTML <head> during SSR.

Requirements

  • BrowserRouteDataProvider wraps your app with SEOProvider (included by default).
  • For SSR, the framework's render wraps the tree with SEOProvider, captures SEO props from <SEO />, and injects the head tags before </head>. No external dependencies.

Usage

Import SEO from lovable-ssr and add it to any page:

tsx
import { SEO } from 'lovable-ssr';

function VideoPage({ video }) {
  const siteUrl = import.meta.env.VITE_SITE_URL ?? 'https://example.com';

  return (
    <div>
      <SEO
        title={`${video.title} | My App`}
        description={video.description ?? video.title}
        url={`${siteUrl}/video/${video.id}`}
        image={video.thumbnail}
        type="video.other"
        structuredData={{
          '@context': 'https://schema.org',
          '@type': 'VideoObject',
          name: video.title,
          description: video.description,
          thumbnailUrl: video.thumbnail,
          uploadDate: video.created_at,
        }}
      />
      {/* rest of page */}
    </div>
  );
}

Props

PropTypeRequiredDescription
titlestringYesDocument title (<title>, og:title, twitter:title).
descriptionstringYesMeta description (meta name="description", og:description, twitter:description).
imagestringNoImage URL for Open Graph and Twitter cards.
urlstringNoCanonical URL and og:url.
typestringNoog:type (default: "website"). Use "video.other", "profile", etc. for specific types.
noindexbooleanNoIf true, adds meta name="robots" content="noindex, nofollow".
structuredDataobjectNoJSON-LD object; rendered as <script type="application/ld+json">.

Example: homepage

tsx
<SEO
  title="Game Haven Hub - Gameplays e Comunidade Gamer"
  description="Assista gameplays, descubra criadores de conteúdo e faça parte da maior comunidade gamer."
  url={siteUrl}
  image={`${siteUrl}/og-image.png`}
  structuredData={{
    '@context': 'https://schema.org',
    '@type': 'WebSite',
    name: 'Game Haven Hub',
    url: siteUrl,
    description: 'Assista gameplays e descubra criadores de conteúdo.',
  }}
/>

Sitemap and robots.txt

The framework generates /sitemap.xml and /robots.txt automatically from the route registry when sitemap.siteUrl is set in createServer.

1. Enable in server config

ts
createServer({
  root,
  entryPath: 'src/ssr/entry-server.tsx',
  sitemap: { siteUrl: process.env.VITE_SITE_URL ?? 'https://example.com' },
})
  .then((server) => server.listen());

2. Add sitemap config to routes

Each route can opt in with sitemap: { include: true, ... }:

ts
// Static route
{ path: '/', Component: Index, isSSR: true, sitemap: { include: true, changefreq: 'daily', priority: 1.0 } },
{ path: '/search', Component: SearchPage, isSSR: true, sitemap: { include: true, changefreq: 'daily', priority: 0.8 } },

// Dynamic route — use getEntries to return all URLs
{
  path: '/video/:id',
  Component: VideoPage,
  isSSR: true,
  sitemap: {
    include: true,
    changefreq: 'weekly',
    priority: 0.8,
    getEntries: async ({ siteUrl }) => {
      const api = new ApiClient();
      const { data } = await api.getFeedVideos({ limit: 500 });
      return (data?.videos ?? []).map((v) => ({
        loc: `${siteUrl}/video/${v.id}`,
        lastmod: v.created_at?.split('T')[0],
      }));
    },
  },
},

SitemapRouteConfig

FieldTypeDescription
includebooleanMust be true to include in sitemap.
changefreq'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'How often the page changes.
prioritynumber0.0–1.0.
getEntries(ctx: { siteUrl: string }) => Promise<SitemapEntry[]>For dynamic routes: returns { loc, lastmod?, changefreq?, priority? }[]. Required when the path has params (e.g. :id).

For static routes (no :param), omit getEntries; one entry is added automatically.