Return a meta key from any loader to set per-page SEO tags: Open Graph, Twitter Cards, canonical URLs, and hreflang.
// pages/blog/[slug].ts export function loader({ params }) { const post = db.get('SELECT * FROM posts WHERE slug = ?', params.slug); return { post, meta: { title: post.title, description: post.excerpt, image: `https://example.com/images/${post.cover}`, }, }; }
meta is just another key in the return value.
| Field | Generated Tags |
|---|---|
title | <title>, og:title |
description | <meta name="description">, og:description |
image | og:image, twitter:image, twitter:card |
canonical | <link rel="canonical"> |
robots | <meta name="robots"> |
type | og:type (default: "website") |
The page title is formatted as "{meta.title} | {config.title}". If no meta.title is set, the config title is used alone. This applies to both <title> and og:title.
Layout loaders can also return meta. The merge order is: root layout → nested layout → page (last wins). This lets a root layout set defaults (e.g., a site-wide image) that individual pages can override.
// pages/_layout.ts - set site-wide defaults export function loader() { return { meta: { image: 'https://example.com/default-og.jpg', type: 'website', }, }; } // pages/blog/[slug].ts - page overrides export function loader({ params }) { const post = getPost(params.slug); return { post, meta: { title: post.title, description: post.excerpt, type: 'article', // overrides layout's "website" }, }; }
When i18n is configured, <link rel="alternate" hreflang="..."> tags are generated automatically on every page. No user action needed. This includes an x-default tag pointing to the default locale URL.
With meta.title = "Hello World" and site title "My Blog":
<title>Hello World | My Blog</title> <meta name="description" content="..."> <meta property="og:title" content="Hello World | My Blog"> <meta property="og:description" content="..."> <meta property="og:image" content="https://..."> <meta property="og:type" content="website"> <meta property="og:url" content="/blog/hello-world"> <meta property="og:locale" content="en"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:image" content="https://..."> <link rel="canonical" href="/blog/hello-world">
If no meta key is returned from loaders, behavior is unchanged. The global config title is used and no extra tags are added.