Add SEO
Adds Open Graph tags, Twitter cards, a sitemap, robots.txt, and favicons. Runs once; updates apply to every route.
This kit already ships SEO
app/lib/seo.ts, the per-page meta/OG/Twitter/JSON-LD, and the
robots.txt / sitemap.xml / llms.txt generator (scripts/generate-seo.mjs)
are already wired. In most cases you only edit SITE_URL in
app/lib/routes-manifest.ts and BRAND / BRAND_DESCRIPTION in
app/lib/brand.ts, then the three files regenerate on pnpm dev / pnpm build
(or pnpm gen:seo). The sections below explain each piece and how to extend it
— see SEO & LLMO for the everyday workflow.
1. Open Graph + Twitter meta (shared helper)
The kit ships app/lib/seo.ts. A from-scratch version looks like:
type SeoInput = {
title: string
description: string
path?: string
image?: string
}
const SITE_NAME = 'Your site name'
const SITE_URL = import.meta.env.VITE_SITE_URL ?? 'http://127.0.0.1:3000'
export function seo({ title, description, path = '/', image = '/og-image.png' }: SeoInput) {
const url = `${SITE_URL}${path}`
return [
{ title: `${title} — ${SITE_NAME}` },
{ name: 'description', content: description },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ property: 'og:url', content: url },
{ property: 'og:type', content: 'website' },
{ property: 'og:image', content: image },
{ property: 'og:site_name', content: SITE_NAME },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: title },
{ name: 'twitter:description', content: description },
{ name: 'twitter:image', content: image },
] as const
}Update each route's head() to use it:
import { seo } from '~/lib/seo'
head: () => ({
meta: seo({
title: 'About',
description: 'Learn who is behind this and what we set out to do.',
path: '/about',
}),
}),2. robots.txt, sitemap.xml, llms.txt (already generated)
You do not hand-write these — the kit generates all three as real static
files in public/ via scripts/generate-seo.mjs, from the publicRoutes list
and SITE_URL in app/lib/routes-manifest.ts (plus BRAND / BRAND_DESCRIPTION
for llms.txt). The dev and build scripts run the generator first, so the
files stay in sync; run it on demand with:
pnpm gen:seoTo change what's crawlable, edit publicRoutes in app/lib/routes-manifest.ts
and regenerate — never edit public/robots.txt / public/sitemap.xml /
public/llms.txt by hand (they're overwritten on the next run). Set the real
domain by changing SITE_URL in the same file.
4. Favicons
Add to public/:
favicon.ico(32x32)apple-touch-icon.png(180x180)icon.svg(optional, for modern browsers)
Add to <head> via links in app/routes/__root.tsx:
links: [
{ rel: 'stylesheet', href: appCss },
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
],5. Canonical + lang
<html lang="en">already set in__root.tsx; change to the correct language if needed.- Add a canonical link per route:
{ rel: 'canonical', href: url }insideseo().
6. Verify
- Open each route. View source: titles, meta, OG tags present.
- Run Lighthouse SEO audit in Chrome devtools: should score 95+.
- Twitter card validator and Facebook debugger: paste URLs to confirm OG renders.