Introduction
When building a Next.js blog or website, it's important to include Open Graph (OG) images for better social sharing experiences. OG images enhance how your links are presented when shared on platforms like Facebook, Twitter, and LinkedIn, giving them a more professional and clickable appearance. In this guide, I'll walk you through how to dynamically generate OG images in Next.js 13, using the new app directory and dynamic routes.
We'll also explore common pitfalls and how to troubleshoot them in a production environment.
Step 1: Setting Up Your Project
Ensure you have a Next.js 13 and above project with the app router. If you're using TypeScript, configure that as well. Here's how your project should be structured:
project-root/
|-- src/
| |-- app/
| | |-- blog/
| | | |-- [slug]/
| | | | |-- page.tsx
| | | | |-- opengraph-image.tsx
| |-- components/
| |-- public/
|-- next.config.js
Step 2: Creating the OG Image Route
In Next.js 13, Open Graph image generation can be done using a special route, which you’ll place inside the dynamic [slug]
folder for your blog.
Create a file at src/app/blog/[slug]/opengraph-image.tsx
. This file is responsible for generating the OG image based on the blog post slug.
import { ImageResponse } from 'next/og';
export const runtime = 'edge'; // Ensure you are using Edge runtime for faster responses
export default async function Image({ params }: { params: { slug: string } }) {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/post/${params.slug}`);
if (!res.ok) {
throw new Error(`Failed to fetch post data: ${res.status}`);
}
const { frontMatter } = await res.json();
const featuredImage = frontMatter.featuredImage || '/images/default-image.jpg';
return new ImageResponse(
(
<div style={{
height: '100%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundImage: `url(${process.env.NEXT_PUBLIC_SITE_URL + featuredImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
fontFamily: "'Outfit', sans-serif",
fontSize: 60,
fontWeight: 600,
color: '#ffffff',
textShadow: '4px 4px 8px rgba(0, 0, 0, 0.8)',
}}>
<h1 style={{
zIndex: 1,
color: '#ffffff',
WebkitTextStroke: '2px black',
padding: '0 20px',
}}>
{params.slug.replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase())}
</h1>
</div>
),
{
width: 1200,
height: 630,
}
);
} catch (error) {
console.error("Error generating OG image:", error);
return new Response('Failed to generate Open Graph image', { status: 500 });
}
}
Breakdown:
- Edge Runtime: We use
runtime: 'edge'
for faster image generation. If you face issues in production with this, switch tonodejs
. - Fetching Post Data: The function fetches blog post data using a dynamic route and builds the Open Graph image with the post's featured image and title.
Step 3: Setting Up API for Blog Post Data
To serve the blog post data (like title, featured image, etc.), you’ll need an API route. Create the file src/pages/api/post/[slug].ts
:
import { getPostData } from '@/lib/posts';
export default async function handler(req, res) {
const { slug } = req.query;
try {
const post = await getPostData(slug);
res.status(200).json(post);
} catch (error) {
res.status(404).json({ error: 'Post not found' });
}
}
Fetch Post Data Function
Ensure your getPostData
function (in /src/lib/posts.ts
) fetches the necessary front matter and content of the post, including the featured image.
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDirectory = path.join(process.cwd(), 'content/posts');
export async function getPostData(slug: string) {
const fullPath = path.join(postsDirectory, `${slug}.mdx`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
return { frontMatter: data, content };
}
Step 4: Updating the Blog Layout
For each blog post, include the Open Graph meta tags so the dynamic images are picked up by social platforms. Update your blog layout (e.g., src/app/blog/[slug]/page.tsx
) to include this.
import Head from 'next/head';
export default function BlogPost({ params }) {
const { slug } = params;
// Fetch post data here
const post = await getPostData(slug);
return (
<>
<Head>
<meta property="og:image" content={`${process.env.NEXT_PUBLIC_SITE_URL}/blog/${slug}/opengraph-image`} />
</Head>
{/* The rest of your blog layout */}
</>
);
}
Step 5: Testing Locally and Troubleshooting
Once everything is set up, test the OG image functionality locally. If everything works locally but fails in production, ensure the following:
- Environment Variables: Make sure
NEXT_PUBLIC_SITE_URL
is correctly set on both local and production environments. - Edge Runtime: Some issues may arise from using the
edge
runtime. If errors persist, try switching tonodejs
in production. - Caching Issues: If images don’t update properly, clear the cache on Vercel or your deployment service.
Step 5: Testing the OG Image and API
Once you have implemented the Open Graph image generation, it's important to test the API to ensure everything is working correctly and that the OG image and text are being generated as expected.
Testing the API Locally
To test the API route and ensure that the image and text are being generated correctly:
-
Start your local Next.js server: Make sure your development server is running by executing:
npm run dev
Access the API endpoint: Open your browser and navigate to the dynamic Open Graph image URL, using a specific blog post slug. The URL structure should look like this:
http://localhost:3000/blog/[slug]/opengraph-image
Replace [slug] with the actual slug of your blog post, e.g., my-first-post. So the complete URL would be:
http://localhost:3000/blog/my-first-post/opengraph-image
View the Generated Image: When you visit this URL, you should see the dynamically generated OG image based on the blog post's featured image and title. This image is what will appear when your post is shared on social media platforms.
Testing the API Response To verify that the API is returning the correct data for the OG image:
Inspect the API route: Use tools like Postman or the browser's network inspector to check the response from your API. The API should fetch the blog post data, including the title and featured image.
Access the API route directly:
http://localhost:3000/api/post/[slug]
For example, for the same my-first-post slug:
http://localhost:3000/api/post/my-first-post
Check the JSON response: The API should return a JSON response with the front matter of your blog post, including fields like title and featuredImage. Ensure that this data is correct and matches the expected content of the blog post.
Example response:
{
"frontMatter": {
"title": "My First Post",
"featuredImage": "/images/my-first-post.jpg"
},
"content": "The content of the post..."
}
Troubleshooting If the OG image is not generating correctly or the API is not returning the correct data, ensure the following:
Slug is correct: Verify that the [slug] matches the filename in your content directory. API is working: Use logging or error handling to diagnose if the API route is properly fetching the blog post data. Environment variables: Ensure that NEXT_PUBLIC_SITE_URL is set up properly in your local environment as well as in production. By testing both the API and the generated image locally, you can be confident that the dynamic OG image functionality is working as expected before deploying to production.
Step 6: Deploying
Finally, deploy your application to Vercel or any other hosting provider. After deployment, test the Open Graph image generation on social media platforms like Facebook or LinkedIn.
Conclusion
Setting up dynamic OG images in Next.js 13 can significantly improve the appearance of your blog posts when shared on social media. With dynamic OG image generation, you ensure that each post is visually appealing and properly formatted for better click-through rates.
If you face any issues during the setup, always check the logs for fetch
errors, and make sure your environment variables are configured correctly.
Happy coding! 🎉
Join Our Dev Community
Be the first to get exclusive updates, insights, and tech tips from our vibrant community. Join us to stay ahead in the tech world!