How to Fix: opengraph image does not work when inside a route group
Why Open Graph images break inside a Next.js route group and how to fix it
If your opengraph-image file works at the app root but suddenly fails after moving it into a route group like (marketing), the problem is not your image generation logic. The failure comes from how Next.js App Router metadata file conventions interact with route groups, which are organizational folders that do not exist in the final URL path.
Table of Contents
In the reported issue, the application places an Open Graph image metadata route inside a route group. The page itself resolves correctly, but the generated OG image endpoint does not. That mismatch is the clue: route groups affect file organization, but metadata conventions still need to resolve to a valid segment tree at build and runtime.
Understanding the Root Cause
In the Next.js App Router, folders wrapped in parentheses such as (site) or (marketing) are route groups. They help organize layouts and pages without changing the public URL structure. For example:
app/(marketing)/about/page.tsx
still renders at:
/about
That behavior is usually harmless for pages and layouts. However, metadata files like opengraph-image.tsx, twitter-image.tsx, and icon.png are resolved using special conventions. These files are not ordinary components; they become metadata routes.
The bug appears because the metadata route resolver can fail when the file is nested inside a route group and expected to map cleanly to a URL segment that does not literally exist on disk. In other words, the page route ignores the route group in the final URL, but the Open Graph image route generation may not consistently normalize that grouped segment the same way.
This can lead to symptoms such as:
- The page loads correctly, but the OG image URL returns 404 or fails to generate.
- The <meta property="og:image"> tag points to a broken metadata endpoint.
- The issue only happens after moving the metadata file into a folder wrapped in parentheses.
Technically, this is a framework-level edge case in the interaction between:
- App Router segment resolution
- metadata file conventions
- route group path stripping
- dynamic image generation routing
The important takeaway is simple: metadata files are more sensitive to directory placement than normal pages.
Step-by-Step Solution
The safest fix is to place the opengraph-image file in a route segment that maps directly to the public URL, not inside a route group folder.
1. Identify the broken structure
A structure like this is likely to trigger the bug:
app/
(marketing)/
blog/
page.tsx
opengraph-image.tsx
Even though blog resolves publicly as /blog, the OG metadata route may fail because it is nested under (marketing).
2. Move the metadata file outside the route group
Restructure the route so the metadata file lives in the actual route segment path:
app/
blog/
page.tsx
opengraph-image.tsx
(marketing)/
blog/
page.tsx
In practice, you usually should not duplicate the page like that. A better pattern is to move the actual route segment outside the group if the OG image belongs to that segment.
Preferred structure:
app/
blog/
page.tsx
opengraph-image.tsx
layout.tsx
If you need shared marketing-specific layout behavior, move that concern into a parent layout that does not force the metadata file into a grouped directory.
3. Keep your OG image implementation standard
Your opengraph-image.tsx should follow the normal metadata route pattern:
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default function OpenGraphImage() {
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'white',
fontSize: 64,
}}
>
Blog
</div>
),
{
...size,
}
)
}
4. Verify the generated metadata URL
Run the app and inspect the page source. Confirm that the og:image tag points to a working endpoint for the route you expect. Then open that image URL directly in the browser.
npm run dev
Check:
- The page source contains a valid og:image URL.
- The URL returns an image, not an error page.
- The route works both in development and production.
5. If you must keep route groups, use an explicit metadata object fallback
If reorganizing files is difficult, another workaround is to stop relying on the colocated metadata file convention for that route and instead define metadata explicitly in the page or layout:
import type { Metadata } from 'next'
export const metadata: Metadata = {
openGraph: {
images: ['/api/og/blog'],
},
}
Then create a dedicated route handler or image endpoint outside the problematic route group structure:
app/
api/
og/
blog/
route.ts
This avoids the metadata file convention entirely and gives you full control over the URL.
6. Production-safe workaround with a dedicated route
If you want a robust solution that is unaffected by route group behavior, generate the image from a dedicated route handler:
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export async function GET() {
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#fff',
color: '#111',
fontSize: 72,
}}
>
Blog Preview
</div>
),
{
width: 1200,
height: 630,
}
)
}
Then reference it through metadata:
import type { Metadata } from 'next'
export const metadata: Metadata = {
openGraph: {
images: ['/api/og/blog'],
},
twitter: {
images: ['/api/og/blog'],
},
}
This workaround is especially useful while waiting for an upstream framework fix.
Common Edge Cases
Dynamic segments
If your route looks like app/blog/[slug]/opengraph-image.tsx, make sure the image route receives and uses the correct params. Route groups can make debugging harder because the page may resolve while the metadata route fails independently.
Static export limitations
If you are using a deployment mode that does not support dynamic image generation, your OG image may fail for unrelated reasons. Confirm that your hosting platform supports Edge Runtime or the selected runtime for next/og.
Conflicting metadata definitions
If you define both a file-based opengraph-image.tsx and a manual metadata.openGraph.images configuration, the final output may not be what you expect. Keep one clear source of truth.
Incorrect relative asset loading
If your generated image depends on local fonts or images, verify those assets resolve correctly in the OG generation context. A route group bug can hide a second issue involving asset paths.
Dev vs production differences
Some metadata route problems appear only after build. Always test with:
npm run build
npm run start
If it works in development but not in production, inspect the generated metadata output and server logs.
FAQ
Why do pages work inside route groups but opengraph-image.tsx does not?
Because pages are resolved through normal App Router path normalization, while metadata files use a special resolver. Route groups are stripped from public URLs, and that can expose inconsistencies in metadata route generation.
Is this a bug in my code or a bug in Next.js?
If your OG image works outside the route group and fails only after moving it inside one, it is most likely a framework routing edge case, not an error in your image component.
What is the safest workaround right now?
The most reliable fix is to place the Open Graph metadata file outside the route group or replace it with an explicit image endpoint such as an API route or dedicated route handler referenced from the page metadata.
Until the framework fully normalizes metadata routes across grouped segments, the best engineering decision is to treat route groups as a risky location for file-based metadata assets. Keep opengraph-image.tsx in real route segments, or define the image URL explicitly for full control and predictable production behavior.