SEO
Metadata and SEO
Use Vista's Next-shaped metadata API, robots/sitemap helpers, OG images, and JSON-LD for crawlable SSR HTML.
Why Vista ranks
Vista SSR/SSG-renders real HTML. Put titles, descriptions, canonicals, and structured data in metadata (or generateMetadata) and they land in the document head on every request — not only after client hydration.
Static `metadata` and `generateMetadata`
app/root.tsx
import type { Metadata } from 'vista'
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: {
default: 'My App',
template: '%s | My App',
},
description: 'Built with Vista',
robots: { index: true, follow: true },
openGraph: {
type: 'website',
images: [{ url: '/opengraph-image', width: 1200, height: 630 }],
},
other: {
'theme-color': '#111111',
},
}Nested layouts and pages deep-merge. Title templates inherit from parents. Page-level fields win on scalars; nested openGraph, twitter, robots, alternates, and icons merge.
app/docs/[...slug]/page.tsx
import type { Metadata } from 'vista'
export async function generateMetadata({ params }): Promise<Metadata> {
const doc = await loadDoc(params)
return {
title: doc.title,
description: doc.summary,
alternates: { canonical: `/docs/${doc.slug}` },
}
}Import helpers from vista or vista/metadata.
Robots, sitemap, and manifest
Scaffold with:
Terminal
vista g seoThat writes app/robots.ts, app/sitemap.ts, and app/manifest.ts using typed helpers:
app/robots.ts
import { robots } from 'vista/metadata'
export default function robotsTxt() {
return robots({
rules: { userAgent: '*', allow: '/' },
sitemap: 'https://example.com/sitemap.xml',
})
}app/sitemap.ts
import { sitemap } from 'vista/metadata'
export default function sitemapXml() {
return sitemap([
{ url: 'https://example.com', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
])
}Routes resolve to /robots.txt, /sitemap.xml, and /manifest.webmanifest. You can also export GET that returns the helper Response, or a Next-style default that returns plain data — Vista serializes both.
Set VISTA_SITE_URL (or NEXT_PUBLIC_SITE_URL) for absolute URLs in starters.
Open Graph images
Add app/opengraph-image.tsx (or twitter-image.tsx) in any route segment:
app/opengraph-image.tsx
import { ImageResponse } from 'vista/og'
export default function Image() {
return new ImageResponse(
(
<div style={{ fontSize: 64, background: '#111', color: '#fff', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
My App
</div>
),
{ width: 1200, height: 630 }
)
}Point metadata at /opengraph-image. Static files like opengraph-image.png work the same way. ImageResponse uses Satori + resvg (same stack as Next).
JSON-LD
tsx
import { JsonLd } from 'vista/metadata'
export default function Page() {
return (
<>
<JsonLd
data={{
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Hello',
}}
/>
{/* page */}
</>
)
}jsonLd(data) returns the serialized string if you need to embed it yourself. Angle brackets are escaped for XSS safety.
Checklist
- Unique title, description, and alternates.canonical per important URL
- metadataBase so relative OG/canonical URLs resolve
- /robots.txt + /sitemap.xml from day one (vista g seo or starter defaults)
- Raster OG image (1200×630) via opengraph-image or PNG
- Article/WebSite JSON-LD where it helps rich results