15. Meta и Links функции
Remix предоставляет функции meta и links для управления метатегами страницы и подключения стилей/ресурсов прямо из маршрутов.
meta функция
Заголовок раздела «meta функция»import type { MetaFunction } from "@remix-run/node";
export const meta: MetaFunction = () => { return [ { title: "Мой блог — Технические статьи" }, { name: "description", content: "Статьи о веб-разработке и React" }, { property: "og:title", content: "Мой блог" }, { property: "og:type", content: "website" }, { property: "og:image", content: "https://example.com/og.jpg" }, { name: "twitter:card", content: "summary_large_image" }, ];};Динамические meta из loader
Заголовок раздела «Динамические meta из loader»export async function loader({ params }) { const post = await getPost(params.slug); if (!post) throw new Response("Not Found", { status: 404 }); return json({ post });}
export const meta: MetaFunction<typeof loader> = ({ data }) => { if (!data) { return [{ title: "Статья не найдена" }]; } return [ { title: data.post.title }, { name: "description", content: data.post.excerpt }, { property: "og:title", content: data.post.title }, { property: "og:description", content: data.post.excerpt }, { property: "og:image", content: data.post.coverImage }, { property: "og:url", content: \`https://blog.example.com/\${data.post.slug}\` }, { name: "twitter:card", content: "summary_large_image" }, ];};Наследование meta от родителей
Заголовок раздела «Наследование meta от родителей»// Дочерний маршрут может включать meta родителейexport const meta: MetaFunction = ({ matches }) => { const parentMeta = matches.flatMap(m => m.meta ?? []); return [ ...parentMeta, // включаем родительские meta { title: "Конкретная страница | Родительский заголовок" }, ];};links функция
Заголовок раздела «links функция»import type { LinksFunction } from "@remix-run/node";import styles from "~/styles/page.css?url";
export const links: LinksFunction = () => { return [ // CSS файл { rel: "stylesheet", href: styles }, // Preload ресурс { rel: "preload", href: "/fonts/Inter.woff2", as: "font", crossOrigin: "anonymous" }, // Favicon { rel: "icon", href: "/favicon.svg", type: "image/svg+xml" }, // Canonical URL { rel: "canonical", href: "https://example.com/about" }, ];};Динамические links
Заголовок раздела «Динамические links»export const links: LinksFunction = () => { if (process.env.NODE_ENV === "development") { return []; // Нет prefetch в dev режиме }
return [ { rel: "prefetch", href: "/dashboard" }, { rel: "preconnect", href: "https://fonts.googleapis.com" }, ];};SEO лучшие практики
Заголовок раздела «SEO лучшие практики»export const meta: MetaFunction<typeof loader> = ({ data, location }) => { const url = \`https://example.com\${location.pathname}\`;
return [ { title: \`\${data?.post.title} | Мой блог\` }, { name: "description", content: data?.post.excerpt?.slice(0, 160) }, // Open Graph { property: "og:title", content: data?.post.title }, { property: "og:description", content: data?.post.excerpt }, { property: "og:url", content: url }, { property: "og:type", content: "article" }, { property: "article:published_time", content: data?.post.createdAt }, // Twitter { name: "twitter:site", content: "@mysite" }, { name: "twitter:title", content: data?.post.title }, // Robots { name: "robots", content: "index, follow" }, ];};