Превью сайта с разметкой Open Graph: как автоматически рендерить картинку

от автора

Всем привет! Меня зовут Николай Каменев, я фронтенд-разработчик в Почтатехе. Мы разрабатываем UI для порталов и приложений Почты России.

Я хочу поделиться коротким гайдом, как автоматически рендерить og:image-изображения для превью сайтов. 

Дальше будет небольшая вводная для тех, кто не знаком со сборкой превью по технологии Open Graph. Если вы и так все знаете, переходите сразу в следующий раздел 🙂

О технологии Open Graph

Open Graph позволяет сформировать превью сайта, которое будет отображаться при публикации ссылки на каком-либо ресурсе. Его используют почти все соцсети, мессенджеры и интернет-сервисы. 

По стандарту, превью собирается из элементов, помеченных определенными тегами. Они добавляются в тег head. С отображением картинки в превью (ее помечают тегом og:image) бывают проблемы. У каждой площадки свои требования к размеру превью, поэтому исходная картинка может некорректно кадрироваться под нужное окно. Чтобы этого избежать, приходится вручную все правильно настраивать. Моя инструкция поможет сделать это автоматически.

Пример превью в разметке Open Graph

Настраиваем автоматический рендеринг

Я буду генерировать og:image с помощью Next.js. Вы можете использовать любой другой фреймворк, главное, стоит помнить, что изображение формируется на сервере.

0. Инициализируем проект

Создаем приложение командой:

yarn create next-app --typescript

и запускаем его:

yarn dev
Стартовая страница после генерации Next.js-приложения
Стартовая страница после генерации Next.js-приложения

Открывается страница приложения — значит, все работает, и можно продолжать.

Для генерации изображений возьмем библиотеку Puppeteer для Node.js. Она дает возможность использовать Headless Chrome Node.js API. С ее помощью мы можем запустить страницу в браузере Chrome на беке, сделать скриншот и отправить клиенту как og:image.

Меньше слов, больше дела. Устанавливаем Puppeteer:

yarn add puppeteer

1. Пишем функцию — обработчик запроса

Переходим в папку /api, создаем файл og-image.ts и пишем функцию OGImage, которая принимает request и отдает response (подробнее про Next.js API — в документации).

// pages/api/og-image.ts  import {NextApiRequest, NextApiResponse} from "next";  export default async function OGImage(req: NextApiRequest, res: NextApiResponse) {    try {           } catch (e) {           } }

2. Формируем интерфейс запроса данных для картинки 

Теперь нужно решить, каким образом будем передавать в запросе данные для картинки. Я выбрал метод query. Он позволяет использовать простой запрос GET с параметрами. Пример запроса: https://example.com/api/og-image?title=”Test”

Определяем, что мы ожидаем от клиента title и description, создаем файл types.ts в папке _lib и описываем интерфейс request.

// pages/api/_lib/types.ts  export interface ParsedRequest {    title?: string;    desc?: string; }

3. Пишем функцию для получения параметров запроса

Создаем parser.ts в папке _lib. Функция будет обрабатывать запрос и возвращать заголовок и описание.

// pages/api/_lib/parser.ts  import {NextApiRequest} from "next"; import {ParsedRequest} from "./types";  export function parseRequest(req: NextApiRequest) {    const { title, desc } = req.query as ParsedRequest;     return {        title,        desc,    }; }

4. Делаем шаблон разметки картинки

Параметры получили, теперь самое время заняться оформлением картинки. Для упрощения стилизации я буду использовать библиотеку готовых компонентов MUI.

Создаем React-компонент (можно использовать чистый html):

// pages/api/_lib/template.tsx  export const OGImage: FC<ParsedRequest> = ({title, desc}) => {    return (        <Box height='100%' width='100%' bgcolor='#1937ff' display='flex'>                    <Stack m='auto'>                        <Typography variant='h1' color='white'>                            {title}                        </Typography>                        <Typography variant='h2' color='white'>                            {desc}                        </Typography>                    </Stack>        </Box>    ) }

и функцию для рендеринга React-компонента в html-код:

// pages/api/_lib/template.tsx  export function getHtml(parsedReq: ParsedRequest) {    const { title, desc } = parsedReq;     return renderToString(<OGImage title={title} desc={desc} />); }

Осталось отрендерить эту страницу и сделать скриншот. Снова переходим в папку _lib и создаем файл render.ts. В нем сделаем функцию для генерации браузерной страницы и сохранения скриншота. Функция принимает html-код, который мы получили выше.

// pages/api/_lib/render.ts  import core from 'puppeteer';  export async function getScreenshot(html: string) {    const browser = await core.launch();    const page = await browser.newPage();       await page.setViewport({ width: 1200, height: 630, deviceScaleFactor: 2 });    await page.setContent(html);    return await page.screenshot({ type: 'png' }); }

Ширину и высоту я указал оптимальную для разных платформ, deviceScaleFactor позволяет увеличить изображение в два раза для устройств с высоким разрешением экрана. Тип скриншота можно выбрать любой (png, jpeg, webp).

5. Собираем превью

// pages/api/og-image.ts  import {NextApiRequest, NextApiResponse} from "next"; import {parseRequest} from "./_lib/parser"; import {getHtml} from "./_lib/template"; import {getScreenshot} from "./_lib/render";  export default async function OGImage(req: NextApiRequest, res: NextApiResponse) {    try {        const parsedReq = parseRequest(req);        const html = getHtml(parsedReq);        const file = await getScreenshot(html);        res.statusCode = 200;        res.setHeader('Content-Type', `image/png`);        res.setHeader('Cache-Control', `public, immutable, no-transform, s-maxage=31536000, max-age=31536000`);        res.end(file);    } catch (e) {        res.statusCode = 500;        res.setHeader('Content-Type', 'text/html');        res.end('<h1>Internal Error</h1><p>Sorry, there was a problem</p>');        console.error(e);    } }

Переходим по запросу: http://localhost:3000/api/og-image?title=Заголовок&desc=Тестируем%20динамическое%20превью

Вуаля! У нас появляется картинка.

Превью, которое мы получили, используя шаблон из четвертого шага
Превью, которое мы получили, используя шаблон из четвертого шага

6. Добавляем тег в head

И последний шаг:

<meta property="og:image" content="http://localhost:3000/api/og-image?title=Заголовок&desc=Тестируем%20динамическое%20превью">

Заключение

Мы получили быстрое и простое решение для динамического рендеринга превью. Это базовый функционал, в котором используются только текстовые переменные. К нему можно добавить и картинки — достаточно сделать новый шаблон. 

Полный пример проекта, который мы собрали в этой статье, вы можете найти в GitHub-репозитории.


ссылка на оригинал статьи https://habr.com/ru/company/posttech/blog/687726/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *