1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[950],{2704:function(e,t,i){(window.__NEXT_P=window.__NEXT_P||[]).push(["/posts/2021-08-05-i-added-dynamic-open-graph-images-to-my-site",function(){return i(1893)}])},7497:function(e,t,i){"use strict";var s=i(3555);let a=(0,s.zo)("div",{variants:{center:{true:{marginInline:"auto"}},mw:{0:{maxWidth:"44ch"},1:{maxWidth:"75rem"}}}});t.Z=a},3555:function(e,t,i){"use strict";i.d(t,{rS:function(){return h},zY:function(){return r},zo:function(){return a}});var s=i(6744);let{styled:a,getCssText:n,globalCss:r,createTheme:o,config:A,theme:h}=(0,s.Th)({media:{belowI1:"(max-width: 29.999em)",i1:"(min-width: 30em)",belowI2:"(max-width: 44.999em)",i2:"(min-width: 45em)",belowI3:"(max-width: 65.999em)",i3:"(min-width: 65em)",belowI4:"(max-width: 72.999em)",i4:"(min-width: 72em)"},theme:{fonts:{body:"'PT Root UI', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"},colors:{body:"#020202",bodySubtle:"rgb(69, 68, 68)",background:"rgb(253, 206, 245)",documentBorder:"#fff",accentA:"#090efe",accentB:"#f8ce03",accentC:"#e3fffd"},space:{1:"0.5rem",2:"1rem",3:"1.25rem",4:"1.875rem",5:"2.8125rem",page:"15vmin",documentBorderWidth:"$2",small:"$1",medium:"$2",large:"$3"}},utils:{stackInline:e=>({"& > * + *":{marginInlineStart:e}}),stackBlock:e=>({"& > * + *":{marginBlockStart:e}})}})},1893:function(e,t,i){"use strict";i.r(t),i.d(t,{__N_SSG:function(){return A},default:function(){return m},meta:function(){return c},tableOfContents:function(){return h}});var s=i(5893),a=i(1151),n=i(7497),r={src:"/_next/static/media/mia-karmageddon-og-image.668599a7.jpg",height:916,width:1190,blurDataURL:"data:image/jpeg;base64,/9j/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAGAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAeEAABBAMAAwAAAAAAAAAAAAADAAECBAUGERIiYf/EABQBAQAAAAAAAAAAAAAAAAAAAAP/xAAXEQEBAQEAAAAAAAAAAAAAAAABAAIR/9oADAMBAAIRAxEAPwCdxW9anTx1UF3WWtGEAYySkAL+Umb2l126/fqIiTrMab//2Q==",blurWidth:8,blurHeight:6},o={src:"/_next/static/media/blog-post-og-image.0d16ba5c.jpg",height:1210,width:1190,blurDataURL:"data:image/jpeg;base64,/9j/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAIAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAT/xAAfEAABAwMFAAAAAAAAAAAAAAABAAIDBAcSERMhI0H/xAAUAQEAAAAAAAAAAAAAAAAAAAAC/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAESYf/aAAwDAQACEQMRAD8AjjrLS7LDMyJ0mIz6KgknTn1ERKmO8P/Z",blurWidth:8,blurHeight:8},A=!0;let h=[],c={title:"I added dynamic Open Graph images to my site",description:"Using Next.js and Puppeteer, I created a set of dynamic OG images that reuse the design system components from my site.",date:"2021-08-05",tags:["Open Graph Protocol","OG Image"]};function d(e){let t=Object.assign({p:"p",a:"a",em:"em",code:"code"},(0,a.ah)(),e.components),{Image:i}=t;return i||function(e,t){throw Error("Expected "+(t?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Image",!0),(0,s.jsxs)(n.Z,{css:{stackBlock:"$medium"},children:[(0,s.jsxs)(t.p,{children:["Earlier this year, I started adding Open Graph (OG) images to my site. These are the preview images you see when you share a link, like on Twitter or in iMessage. I maintain a ",(0,s.jsx)(t.a,{href:"/this-is-my-jam",children:"collection of my favourite music"}),", and I thought it would be cool to include the album art and track details when I share a link to a track."]}),(0,s.jsx)(i,{src:r,alt:"Screenshot showing the This is My Jam OG image as it appears on Twitter",caption:(0,s.jsxs)(s.Fragment,{children:[" The ",(0,s.jsx)(t.em,{children:"This is My Jam"})," OG image when a track is"," ",(0,s.jsx)(t.a,{href:"https://twitter.com/juice49/status/1383518779867500556",children:" shared on Twitter"})," ."]})}),(0,s.jsxs)(t.p,{children:["The OG image is generated dynamically for every track I post. The template is a regular Next.js page, which a serverless function captures as an image. You can ",(0,s.jsx)(t.a,{href:"/this-is-my-jam/b1683db8-5d7b-494b-be3f-61a4e6c5f8d0/og-image",children:"take a look at the template page here"}
1),", and ",(0,s.jsx)(t.a,{href:"https://github.com/juice49/personal-site/blob/main/src/pages/this-is-my-jam/%5Bid%5D/og-image.tsx",children:"its source code here"}),"."]}),(0,s.jsxs)(t.p,{children:["I like this approach, because it means I can reuse design system components from the site to create the image. Using ",(0,s.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/CSS/zoom",children:"the CSS zoom property"}),", these components can be scaled to look great when captured at 1200x600 pixels. I love laying out web content with Flexbox and Grid, so why not use the same techniques for rendering an image?"]}),(0,s.jsxs)(t.p,{children:["It should even be possible to use ",(0,s.jsx)(t.a,{href:"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/aspect-ratio",children:"media queries"})," to adapt the template to different aspect ratios. The large preview images you see on your Twitter timeline are 2:1, but 1:1 is supported too. I haven't explored this yet, but I think it would work nicely."]}),(0,s.jsxs)(t.p,{children:["Another neat thing about using a Next.js page is that we can use the Next.js data fetching functions (like ",(0,s.jsx)(t.code,{children:"getStaticProps"}),") to add dynamic content. I use the same data fetching function when rendering the OG image that I use when rendering ",(0,s.jsx)(t.a,{href:"/this-is-my-jam/b1683db8-5d7b-494b-be3f-61a4e6c5f8d0",children:"the track detail page"}),"."]}),(0,s.jsxs)(t.p,{children:["By customising the target path in the request URL, the serverless function can capture any page on the site. This means we can add many different OG image templates to a site by adding new Next.js pages. Alongside the specialised OG images for the ",(0,s.jsx)(t.em,{children:"This is My Jam"})," tracks, I created ",(0,s.jsx)(t.a,{href:"/og-image?title=Example+Open+Graph+image&date=2021-08-05",children:"a generic template"})," (",(0,s.jsx)(t.a,{href:"https://github.com/juice49/personal-site/blob/main/src/pages/og-image/index.tsx",children:"source code"}),") that can be customised using the URL query string. The page linking to the OG image can provide a title and, optionally, a date, making it ideal for blog posts and pages."]}),(0,s.jsx)(i,{src:o,alt:"Screenshot showing the article OG image as it appears on Twitter",caption:(0,s.jsxs)(s.Fragment,{children:[" This very article when it's"," ",(0,s.jsx)(t.a,{href:"https://twitter.com/juice49/status/1423176890265870337",children:" shared on Twitter"})," ."]})}),(0,s.jsx)(t.p,{children:"The serverless function uses Puppeteer to capture a 1200x600 pixel snapshot of the template page. The image is generated
1when it's first requested; no work is done at build-time. The generated image is cached by the CDN, so it doesn't have to be generated for every request."}),(0,s.jsxs)(t.p,{children:["I created ",(0,s.jsxs)(t.a,{href:"https://github.com/juice49/next-og-image",children:["the ",(0,s.jsx)(t.em,{children:"Next OG Image"})," project"]})," that wraps this code up for use with any Next.js site. If you have any questions about it, please feel free to ",(0,s.jsx)(t.a,{href:"https://twitter.com/juice49",children:"send me a Tweet"}),"."]})]})}var m=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{wrapper:t}=Object.assign({},(0,a.ah)(),e.components);return t?(0,s.jsx)(t,Object.assign({},e,{children:(0,s.jsx)(d,e)})):d(e)}}},function(e){e.O(0,[774,737],function(){return e(e.s=2704)}),_N_E=e.O()}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.