
Всем привет! Меня зовут Николай Каменев, я фронтенд-разработчик в Почтатехе. Мы разрабатываем 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

Открывается страница приложения — значит, все работает, и можно продолжать.
Для генерации изображений возьмем библиотеку 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/
Добавить комментарий