Nuxtã ã¨ãNuxt SEO Kitã®nuxt-og-imageã使ãã°ã
vueã³ã³ãã¼ãã³ãã¨ãHMLTãOGç»åã«ã§ãããã©ã
unjs/nitroã§ãã§ããªãããªã¨æãã
ãããã調ã¹ã¦ã¿ãã¨ãã®åå¿é²(*´Ïï½*)
nuxt-og-imageã®ä¸ãã®ããã¦ã¿ããã
satoriã¨sharpã使ã£ã¦ãã®ã§ããããåèã«å®è£
ãã¦ã¿ã
satori+sharpã使ã£ãç»åçæ
ãã®3ã¤ã®ã©ã¤ãã©ãªã使ãã¨ãHTMLãPNGã«å¤æã§ããã£ã½ã
- vercel/satori ... ReactNode(VNode)ãSVGã«å¤æ
- lovell/sharp ... SVGãPNGã«å¤æ
- natemoo-re/satori-html ... HTMLãVNodeã«å¤æ
satoriã¯Vercel社ãåºãã¦ããã©ã¤ãã©ãªã§ãJSXå½¢å¼ã§å©ç¨ããã£ã½ãã
ãªã®ã§ãHTMLã¨ãã£ã¦ãReactNodeãåãåãå½¢ã«ãªã£ã¦ããã
jsxå½¢å¼ãªããsatoriã¨sharpã§ãããªæãã«ç»åãçæã§ããã
// api.jsx import satori from 'satori' // satoriã§ReactNodeãSVGã«å¤æ const svg = await satori( <div style={{ color: 'black' }}>hello, world</div>, { width: 600, height: 400, fonts: [] }, ); // sharpã§SVGããPNGã«å¤æ const png = await sharp(Buffer.from(svg)).png().toBuffer();
ãã®ã¾ã¾ã ã¨ä½¿ããªãã®ã§ãnatemoo-re/satori-htmlã使ãã
HTMLãªæååãVNodeã«å¤æããsatriã®ã¤ã³ãããã«ã§ããããã«å¤æãã¦ãããã©ã¤ãã©ãªã
import satori from "satori"; import { html } from "satori-html"; import sharp from "sharp"; // satori-htmlã§æååãVNodeã«å¤æ const vnode = html(`<div style="color: black;">hello, world</div>`); // satoriã§VNodeãSVGã«å¤æ const svg = await satori( vnode, { width: 600, height: 400, fonts: [] }, ); // sharpã§SVGããPNGã«å¤æ const png = await sharp(Buffer.from(svg)).png().toBuffer();
ããã§HTMLæååããPNGãçæã§ãã¡ãããããã(*´Ïï½*)
satoriããã¹ã¦ã®CSSãæ±ããããã§ã¯ãªãããã
ãµãã¼ãç¶æ³ãå¶éäºé
ãªã©ã¯ããã¥ã¡ã³ããåç
§ãã
satoriã§ç»åãæ±ã
ç»åã«ã¤ãã¦ã¯ãbase64å½¢å¼ã«å¤æãã¦ãåãè¾¼ãã°OKã
URLã§ããã°ãsrc="https://...."
ã¨ãã§ããããã
import { html } from "satori-html"; import fs from "fs"; // ç»åãã¡ã¤ã«ã®èªã¿è¾¼ã¿ const imageBuffer = await fs.readFileSync("<image_path>"); // ç»åãã¡ã¤ã«ãbase64å½¢å¼ã«ã¨ã³ã³ã¼ã const imageBase64 = Buffer.from(imageBuffer).toString("base64"); const imageData = `data:image/png;base64,${imageBase64}`; const vnode = html(` <div> <img src="${imageData}" width="1200" height="630"/> </div> `);
satoriã§fontãæ±ã
ãã©ã³ããå¤æ´ãããå ´åã¯ãsatoriã®ãªãã·ã§ã³ã«ã
ãã©ã³ããã¡ã¤ã«ã®ãã¼ã¿ã渡ãã°OK
import satori from "satori"; import { html } from "satori-html"; import fs from "fs"; // fontãã¼ã¿ã®èªã¿åã const fontData = await fs.readFileSync("./NotoSansJP-Bold.ttf"); // satori-htmlã§æååãVNodeã«å¤æ const vnode = html(`<div style="color: black;">hello, world</div>`); // satoriã§VNodeãSVGã«å¤æ const svg = await satori( vnode, { width: 1200, height: 630, // ãã©ã³ãã®è¨å® fonts: [ { name: 'Noto Sans JP', data: fontData, style: 'normal', }, ], // åãè¾¼ã¿ãã©ã³ãã®æå¹å(ããã©ã«ãã¯false) embedFont: true, }, );
nitroã§ç»åçæã§ããããã«ãã
ãã£ã¬ã¯ããªæ§æã¯ãããªæãã
server/ assets/ NotoSansJP-Bold.ttf.b64 og_template.png.b64 routes/ index.ts nitro.config.ts package.json
å®éã«OGPç»åãçæãã
routes/index.ts
ã¯ãããªæãã
// routes/index.ts import satori from "satori"; import { html } from "satori-html"; import sharp from "sharp"; export default defineEventHandler(async (event) => { const { text } = getQuery(event); // fontãã¼ã¿ã®èªã¿åã const fontDataB64 = await useStorage().getItem<String>("assets/server/NotoSansJP-Bold.ttf.b64"); const fontData = Buffer.from(fontDataB64, "base64"); // èæ¯ç»åã®èªã¿è¾¼ã¿ const imageBase64 = await useStorage().getItem<String>("assets/server/og_template.png.b64"); // ç»åãã¡ã¤ã«ãbase64å½¢å¼ã«ã¨ã³ã³ã¼ã const imageData = `data:image/png;base64,${imageBase64}`; // satori-htmlã§æååãVNodeã«å¤æ const vnode = html(` <div style="width: 1200px; height: 630px; position: relative; display: flex; justify-content: center; align-items: center;"> <img style="position: absolute; z-index: -1;" src="${imageData}"></img> <div style="font-size: 68px; text-align: center;">${ text || "Hello World"}</div> </div> `); // satoriã§VNodeãSVGã«å¤æ const svg = await satori( vnode, { width: 1200, height: 630, // ãã©ã³ãã®è¨å® fonts: [ { name: 'Noto Sans JP', data: fontData, style: 'normal', }, ], // åãè¾¼ã¿ãã©ã³ãã®æå¹å(ããã©ã«ãã¯false) embedFont: true, }, ); // sharpã§SVGããPNGã«å¤æ const png = await sharp(Buffer.from(svg)).png().toBuffer(); // ãããã¼ã®è¨å® setHeader(event, "Content-Type", `image/png`); setHeader(event, "Cache-Control", "public, max-age=604800"); return ogImage; });
unstorage
ã®æ±ããã¡ãã£ã¨ã ãºã¤
åºæ¬ã¯ä»ã¾ã§ã®ãçµã¿åãããå½¢ã§ã
fsã®ãããã«unjs/unstorageãã¤ããã
unstorageãã¡ããã¨ç解ã§ãã¦ããªãé¨åããããã©ã
const fontData = await useStorage().getItemRaw<ArrayBuffer>("assets/server/NotoSansJP-Bold.ttf");
ã®ããã«ãgetItemRaw()
ã使ãã¨ã
npm run dev
ã®ã¨ãã¯åé¡ãªãããnpm run build
ãããã¨ã«ããã¾ãåããªãã£ããã
nitroã¯buildæã«.output/
ã«ãã¹ã¦ãé
ç½®ããããã«ãªã£ã¦ããããã
assetsãªã©ã®ãã¡ã¤ã«ã.output/
é
ä¸ã«ã¾ã¨ãããããã
output/server/chunks/raw/og_template.mjs
ãè¦ã¦ã¿ãã¨ã
// .output/server/chunks/raw/og_template.mjs // ROLLUP_NO_REPLACE const og_template = "�PNG\r\n\u001a\n\u0000\u0000\u0000\rIHD
ã¨ããæãã®æååã«ãªã£ã¦ãã¦ããã¾ãArrayBufferã¨ãã¦èªã¿åããã¨ãã§ããªãã£ããã
ãªã®ã§ãããããããBase64å½¢å¼ã«ã¨ã³ã³ã¼ããã.b64
ãã¡ã¤ã«ãç¨æãã¦ããã
ãããå©ç¨ããããã«ãã¦ããã
Base64åã¯ãã³ãã³ãã§å®è¡ããã
$ base64 -i og_template.png -o og_template.png.b64
getItems()
ã®æå®ãã¡ãã£ã¨ã ãºã¤
ã¾ããgetItems()
ã§æå®ããassets/server/
ããããããããããã
Issueãããã£ã¦ããã»ã©ã
assets/
ãã£ã¬ã¯ããªé
ä¸ã®ãã¡ã¤ã«ã«serverå´ã®å¦çã§ã¢ã¯ã»ã¹ããå ´åã¯ã
getItems(assets/server/<filename>)
ã¨ããæãã«ãªãã
ãã®ã¬ã¤ãã®ãããã«æ¸ãã¦ããã¨ããã
nitro.config.ts
ã«ã¢ã»ãããã£ã¬ã¯ããªã追å ã§ãããã
// nitro.config.ts export default defineNitroConfig({ serverAssets: [{ baseName: 'templates', dir: './templates' // Relative to `srcDir` (`server/` for nuxt) }] })
ãã®å ´åã¯ããããªæãã«ãªãããã
// routes/success.ts export default defineEventHandler(async (event) => { return await useStorage().getItem(`assets/templates/success.html`) // or return await useStorage('assets:templates').getItem(`success.html`) })
以ä¸!! ããããããã£ããã©ãnitroã§ãOGPãçæã§ããããã«(*´Ïï½*)
ããè¦ã¦ã¿ãã¨ããããããããªã«ãã£ã¦ãããnuxt-og-imageã¯å大ã ãã(*´Ïï½*)
åèã«ãããµã¤ããã¾
- vercel/satori: Enlightened library to convert HTML and CSS to SVG
- natemoo-re/satori-html: An HTML adapter for Vercel's Satori
- lovell/sharp: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.
- Satori + SvelteKit 㧠OGP ç»åãèªåçæãã
- Base64 ã¨ã³ã³ã¼ãã»ãã³ã¼ããè¡ãæ¹æ³ - Node.js ãç¨ããéçº - Node.js å ¥é
- Install Nuxt OG Image | Nuxt SEO
- Server Assets default path confuse · Issue #914 · unjs/nitro
- base64 ã³ãã³ã | ã³ãã³ãã®ä½¿ãæ¹(Linux) | hydroculã®ã¡ã¢