mirror of
				https://github.com/r-freeman/portfolio.git
				synced 2025-11-04 04:51:11 +00:00 
			
		
		
		
	This commit is contained in:
		
							parent
							
								
									de65e69eec
								
							
						
					
					
						commit
						18161a50e8
					
				@ -17,7 +17,7 @@ const gradients = [
 | 
				
			|||||||
export async function GET(request: Request) {
 | 
					export async function GET(request: Request) {
 | 
				
			||||||
    const fontData = await font
 | 
					    const fontData = await font
 | 
				
			||||||
    const {searchParams} = new URL(request.url)
 | 
					    const {searchParams} = new URL(request.url)
 | 
				
			||||||
    const title = searchParams.get('title')
 | 
					    const text = searchParams.get('text')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new ImageResponse(
 | 
					    return new ImageResponse(
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
@ -44,7 +44,7 @@ export async function GET(request: Request) {
 | 
				
			|||||||
                        '-webkit-background-clip': 'text',
 | 
					                        '-webkit-background-clip': 'text',
 | 
				
			||||||
                        color: 'transparent',
 | 
					                        color: 'transparent',
 | 
				
			||||||
                    }}>
 | 
					                    }}>
 | 
				
			||||||
                    {title}
 | 
					                    {text}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
@ -59,5 +59,5 @@ export async function GET(request: Request) {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    );
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -24,13 +24,14 @@ export const metadata = {
 | 
				
			|||||||
        description: meta.description,
 | 
					        description: meta.description,
 | 
				
			||||||
        images: [
 | 
					        images: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url: `/api/og-image?title=${meta.heading}`,
 | 
					                url: `/api/og-image?text=${meta.heading}`,
 | 
				
			||||||
                width: 1200,
 | 
					                width: 1200,
 | 
				
			||||||
                height: 600,
 | 
					                height: 600,
 | 
				
			||||||
                alt: meta.heading,
 | 
					                alt: meta.heading,
 | 
				
			||||||
                type: 'image/png'
 | 
					                type: 'image/png'
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]
 | 
					        ],
 | 
				
			||||||
 | 
					        type: 'website'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,7 +99,7 @@ export default function Services() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <SimpleLayout
 | 
					        <SimpleLayout
 | 
				
			||||||
            heading="I offer a wide range of digital services to elevate and transform your business"
 | 
					            heading={metadata.heading}
 | 
				
			||||||
            description={metadata.description}
 | 
					            description={metadata.description}
 | 
				
			||||||
            gradient="bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400">
 | 
					            gradient="bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400">
 | 
				
			||||||
            <ul
 | 
					            <ul
 | 
				
			||||||
@ -117,4 +118,4 @@ export default function Services() {
 | 
				
			|||||||
            </ul>
 | 
					            </ul>
 | 
				
			||||||
        </SimpleLayout>
 | 
					        </SimpleLayout>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 80 KiB  | 
@ -0,0 +1,145 @@
 | 
				
			|||||||
 | 
					import {ArticleLayout} from '../../../components/layouts/ArticleLayout'
 | 
				
			||||||
 | 
					import {createSlug} from '../../../lib/createSlug'
 | 
				
			||||||
 | 
					import Image from 'next/image'
 | 
				
			||||||
 | 
					import ogMetaTags from './open-graph-meta-tags.png'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
					    authors: 'Ryan Freeman',
 | 
				
			||||||
 | 
					    title: 'Generating dynamic Open Graph images with Next.js',
 | 
				
			||||||
 | 
					    date: '2024-10-10',
 | 
				
			||||||
 | 
					    description: 'In this post I\'ll talk about how I created dynamic, eye-catching Open Graph images with Next.js for this website.'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const metadata = {
 | 
				
			||||||
 | 
					    ...meta,
 | 
				
			||||||
 | 
					    openGraph: {
 | 
				
			||||||
 | 
					        title: meta.title,
 | 
				
			||||||
 | 
					        description: meta.description,
 | 
				
			||||||
 | 
					        images: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url: `/api/og-image?text=${meta.title}`,
 | 
				
			||||||
 | 
					                width: 1200,
 | 
				
			||||||
 | 
					                height: 600,
 | 
				
			||||||
 | 
					                alt: meta.title,
 | 
				
			||||||
 | 
					                type: 'image/png'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        type: 'website'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (props) => <ArticleLayout
 | 
				
			||||||
 | 
					    title={metadata.title}
 | 
				
			||||||
 | 
					    date={metadata.date}
 | 
				
			||||||
 | 
					    description={metadata.description}
 | 
				
			||||||
 | 
					    slug={createSlug(metadata.title)}
 | 
				
			||||||
 | 
					    {...props} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In this post I\'ll talk about how I created dynamic, eye-catching Open Graph images with Next.js for this website.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What is Open Graph?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Open Graph](https://ogp.me/) is the web standard that allows website owners to manage the appearance of their content when it is shared on social media platforms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In other words, it enables website owners to attract readers to their content, similar to how thumbnails on YouTube videos attract our attention and entice us to click on a video.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Show me the code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Alright, let\'s see how this was achieved in code. Taking the new [Services page](/services) as an example, the first step was to create a new API route located at `/api/og-image`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### API route
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This API route should allow me to specify some text like a page heading and dynamically generate an image, which I can then reference using the Open Graph meta tags on each page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					export async function GET(request: Request) {
 | 
				
			||||||
 | 
					    const { searchParams } = new URL(request.url)
 | 
				
			||||||
 | 
					    const text = searchParams.get('text')
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This was straightforward to implement, here the API route responds to a `GET` request and accepts a `text` parameter in the url. For example, `/api/og-image?text=hello,world`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					return new ImageResponse(
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					            style={{
 | 
				
			||||||
 | 
					                fontSize: 64,
 | 
				
			||||||
 | 
					                background: 'black',
 | 
				
			||||||
 | 
					                width: '100%',
 | 
				
			||||||
 | 
					                height: '100%',
 | 
				
			||||||
 | 
					                display: 'flex',
 | 
				
			||||||
 | 
					                textAlign: 'center',
 | 
				
			||||||
 | 
					                alignItems: 'center',
 | 
				
			||||||
 | 
					                justifyContent: 'center',
 | 
				
			||||||
 | 
					                lineHeight: '1',
 | 
				
			||||||
 | 
					                padding: '0 128px'
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <div
 | 
				
			||||||
 | 
					                style={{
 | 
				
			||||||
 | 
					                    backgroundImage: 'radial-gradient(at right top, rgb(221, 214, 254), rgb(239, 68, 68), rgb(251, 146, 60))',
 | 
				
			||||||
 | 
					                    backgroundClip: 'text',
 | 
				
			||||||
 | 
					                    // @ts-ignore
 | 
				
			||||||
 | 
					                    '-webkit-background-clip': 'text',
 | 
				
			||||||
 | 
					                    color: 'transparent'
 | 
				
			||||||
 | 
					                }}>
 | 
				
			||||||
 | 
					                {text}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        width: 1200,
 | 
				
			||||||
 | 
					        height: 600
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Next, I thought about how I wanted the Open Graph image to look. To keep things simple, I settled on a black background with the text centered both horizontally and vertically.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This was achieved using the [ImageReponse](https://nextjs.org/docs/app/api-reference/functions/image-response) constructor, which allows you to create and style dynamic images using JSX and CSS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Image src={`/api/og-image?text=hello,world`} width="1200" height="600" alt=""/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When you visit the API route and specify some text in the url, you'll get something that looks like the image above. Next, I'll demonstrate how I added the Open Graph meta tags to the Services page to render this dynamic image for social media platforms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Open Graph Meta Tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The last step was to include the Open Graph meta tags on the Services page so that when it's shared, an eye-catching image will appear to draw readers to the content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```javascript
 | 
				
			||||||
 | 
					const meta = {
 | 
				
			||||||
 | 
					    title: 'Services - Ryan Freeman',
 | 
				
			||||||
 | 
					    heading: 'I offer a wide range of digital services to elevate and transform your business',
 | 
				
			||||||
 | 
					    description: 'Whether you need a WordPress website, React app, AWS support or odd coding jobs, I\'m here to help. ' +
 | 
				
			||||||
 | 
					        'As an experienced software engineer, I produce high-quality software that will deliver immediate value for you and your customers.',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const metadata = {
 | 
				
			||||||
 | 
					    ...meta,
 | 
				
			||||||
 | 
					    openGraph: {
 | 
				
			||||||
 | 
					        title: meta.title,
 | 
				
			||||||
 | 
					        description: meta.description,
 | 
				
			||||||
 | 
					        images: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url: `/api/og-image?text=${meta.heading}`,
 | 
				
			||||||
 | 
					                width: 1200,
 | 
				
			||||||
 | 
					                height: 600,
 | 
				
			||||||
 | 
					                alt: meta.heading,
 | 
				
			||||||
 | 
					                type: 'image/png'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        type: 'website'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By exporting the `metadata` object, I was able to specify the Open Graph meta tags for this page. In this example, you can see how I’m fetching data from the new API route, which generates a dynamic image featuring the page heading, sized at 1200 by 600 pixels.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Image src={ogMetaTags} alt=""/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Lastly, I can verify everything is working correctly by loading the page in the browser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Conclusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In this post, I demonstrated how you can create dynamic Open Graph images with Next.js for creating content previews which engage readers and attract them to your content.
 | 
				
			||||||
@ -6,11 +6,30 @@ import {formatDate} from '@/lib/formatDate'
 | 
				
			|||||||
import {getAllArticles} from '@/lib/getAllArticles'
 | 
					import {getAllArticles} from '@/lib/getAllArticles'
 | 
				
			||||||
import type {Article} from '@/types'
 | 
					import type {Article} from '@/types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const metadata = {
 | 
					const meta = {
 | 
				
			||||||
    title: 'Writing - Ryan Freeman',
 | 
					    title: 'Writing - Ryan Freeman',
 | 
				
			||||||
 | 
					    heading: 'Writing on software engineering, and everything in between.',
 | 
				
			||||||
    description: 'All of my long-form thoughts on software engineering, and more, displayed in chronological order.'
 | 
					    description: 'All of my long-form thoughts on software engineering, and more, displayed in chronological order.'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const metadata = {
 | 
				
			||||||
 | 
					    ...meta,
 | 
				
			||||||
 | 
					    openGraph: {
 | 
				
			||||||
 | 
					        title: meta.title,
 | 
				
			||||||
 | 
					        description: meta.description,
 | 
				
			||||||
 | 
					        images: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url: `/api/og-image?text=${meta.heading}`,
 | 
				
			||||||
 | 
					                width: 1200,
 | 
				
			||||||
 | 
					                height: 600,
 | 
				
			||||||
 | 
					                alt: meta.heading,
 | 
				
			||||||
 | 
					                type: 'image/png'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        type: 'website'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Article({article}: { article: Article }) {
 | 
					function Article({article}: { article: Article }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <article>
 | 
					        <article>
 | 
				
			||||||
@ -38,7 +57,7 @@ export default async function Writing() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <SimpleLayout
 | 
					        <SimpleLayout
 | 
				
			||||||
            heading="Writing on software engineering, and everything in between."
 | 
					            heading={metadata.heading}
 | 
				
			||||||
            description={metadata.description}
 | 
					            description={metadata.description}
 | 
				
			||||||
            gradient="bg-gradient-to-r from-pink-500 to-violet-500"
 | 
					            gradient="bg-gradient-to-r from-pink-500 to-violet-500"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
export function createSlug(title: string) {
 | 
					export function createSlug(title: string) {
 | 
				
			||||||
    return title.toLowerCase()
 | 
					    return title.toLowerCase()
 | 
				
			||||||
        .replace(/['?:]+/g, '')
 | 
					        .replace(/['?:.,]+/g, '')
 | 
				
			||||||
        .replace(/[.,\s]+/g, '-')
 | 
					        .replace(/\s+/g, '-')
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user