ScreenshotNeo

BlogHow-to

How to Set Different Open Graph Images for Pages on One Website

Give every page its own Open Graph image by generating og:image from that page’s content or route data. Includes plain HTML, CMS, and Next.js patterns plus a verification checklist.

By the ScreenshotNeo team4 October 20269 min read

To set different Open Graph images for pages on one website, emit a page-specific og:image tag in each page’s HTML head. Choose the image URL from that page’s route, content record, or CMS field, and set og:url to the page’s canonical URL. A single constant in a shared template gives every page the same image unless page-level metadata overrides it. The Open Graph Protocol defines og:image as the representative image URL and og:url as the object’s permanent URL. Open Graph Protocol.

1. Understand what must be page-specific

For each public URL, the server-rendered or statically generated HTML should include the metadata for that exact page. At minimum, provide a title, type, canonical object URL, and image URL. Include descriptive image alt text as well.

<head>
  <meta property="og:title" content="Page-specific title">
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://example.com/articles/example-page">
  <meta property="og:image" content="https://example.com/images/example-page-share.jpg">
  <meta property="og:image:alt" content="A concise description of the image">
</head>

Use absolute image and canonical URLs so a crawler can resolve them without guessing the site origin. The image must be publicly fetchable without login or special cookies. The Open Graph Protocol describes the image alt field as a description of the image, not a caption.

2. Put image selection in the page data

Pick one source of truth for the social image URL, such as a CMS field, a content record property, or a generated route-data file. The shared template should read from that value for each request or build output.

Static HTML or a static-site build

For individual HTML documents, set the tags directly in each document’s <head>. For a static site with a shared layout, pass per-page values into the layout while building each route. This illustrative template shows the data relationship; adapt the template syntax to the generator you use.

<head>
  <meta property="og:title" content="{{ page.title }}">
  <meta property="og:type" content="article">
  <meta property="og:url" content="{{ page.canonicalUrl }}">
  <meta property="og:image" content="{{ page.socialImageUrl | default: site.defaultSocialImageUrl }}">
  <meta property="og:image:alt" content="{{ page.socialImageAlt | default: page.title }}">
</head>

The double-brace expressions are placeholders, not built-in HTML syntax. Replace them with your template engine’s syntax and escape values for HTML attributes. Keep a deliberate site-wide fallback for pages without a selected image.

CMS-backed or server-rendered pages

Add a social image field to the content type if editors choose images independently. Resolve the current page’s record before rendering the head, then use its title, canonical URL, image, and alt description. If the field is empty or its value is unusable, select a known public fallback rather than outputting an empty or relative URL.

<head>
  <meta property="og:title" content="<%= escapeHtml(page.title) %>">
  <meta property="og:type" content="article">
  <meta property="og:url" content="<%= escapeHtml(page.canonicalUrl) %>">
  <meta property="og:image" content="<%= escapeHtml(page.socialImageUrl || site.defaultSocialImageUrl) %>">
  <meta property="og:image:alt" content="<%= escapeHtml(page.socialImageAlt || page.title) %>">
</head>

This uses EJS-like placeholders and an illustrative escaping helper. Use the equivalent safe attribute-escaping method for your server framework. Never interpolate untrusted content directly into HTML.

3. Set page-specific Open Graph metadata in Next.js

In the App Router, use a static metadata export when metadata is fixed for a route, or generateMetadata when it depends on route data. Next.js expects absolute URLs in its Open Graph image examples. See the official Next.js metadata API documentation; use types and parameter shapes matching your installed version.

import type { Metadata } from 'next'

export async function generateMetadata({ params }): Promise<Metadata> {
  const page = await getPage(params.slug)

  return {
    title: page.title,
    openGraph: {
      title: page.title,
      type: 'article',
      url: `https://example.com/articles/${page.slug}`,
      images: [{
        url: page.socialImageUrl || 'https://example.com/images/default-share.jpg',
        alt: page.socialImageAlt || page.title,
      }],
    },
  }
}

getPage is application-specific: replace it with the query used by your CMS or database, and handle a missing record using your app’s not-found behavior. Ensure socialImageUrl is absolute and publicly fetchable. A fallback should be an intentional image, not an empty string.

Use route-segment image files

If each route has a prepared asset, place an opengraph-image.jpg, opengraph-image.png, or opengraph-image.gif in the route segment. Next.js automatically adds the corresponding metadata. For templated cards based on route content, use an opengraph-image.js, .ts, or .tsx file. See Next.js Open Graph image file conventions.

Next.js documents generated images as statically optimized and cached by default, unless the implementation uses request-time APIs, uncached data, or dynamic configuration. The documented file convention accepts JPG, JPEG, PNG, and GIF and has an 8 MB limit; exceeding it causes the build to fail. Check the current documentation for the version installed in your project.

Avoid the parent layout inheritance trap

Next.js can inherit Open Graph metadata from a parent layout when a child route does not define it. When a page defines an openGraph object, that object replaces the parent Open Graph object; omitted fields may not merge as expected. If you need shared fields, deliberately include them in each generated object or build a shared object and add the page-specific image. Inspect output for representative routes after changing metadata.

4. Choose between a prepared image and a generated image

Approach Good fit Consideration
Prepared route image A page has a hand-selected or designed social card. Simple to reason about; keep the file at the route convention or reference its absolute URL.
Generated route image Many pages share a visual template with variable title or category data. Check data freshness and caching behavior. Next.js caches generated images by default unless dynamic conditions apply.
CMS image field Editors need to select or update the social image. Validate missing values and ensure the asset is publicly available.
Build-time page data Pages are generated from files or a content repository. Regenerate the output when image data changes.

5. Verify the metadata crawlers receive

  1. Fetch the final HTML for several routes, including one with a custom image and one using the fallback. Inspect the returned <head> and confirm the expected og:image and canonical og:url.
  2. Check the response a crawler receives. Do not rely solely on metadata inserted after client-side JavaScript runs. Next.js documents handling for HTML-limited bots, including facebookexternalhit; verify your own framework and deployment output.
  3. Open each image URL without being logged in. Confirm it returns the intended image and is not blocked by access controls.
  4. Check the image alt text, canonical URL, and title for accuracy.
  5. Look for duplicate tags or stale metadata left by another head component or parent configuration.
  6. If the HTML is correct but a preview still shows an old card, the platform may be showing a cached preview. Use that platform’s current preview refresh or debugging process; interfaces and behavior differ.

For a quick response inspection, run this command from a terminal, replacing the URL with one of your pages:

curl -sL https://example.com/articles/example-page | grep -iE 'og:(title|type|url|image|image:alt)'

This checks HTML text returned by the site. It does not prove that a particular social platform has refreshed its cached preview.

6. Troubleshooting

Symptom Likely cause Fix
Every page shows the same image The shared layout emits a fixed image URL, or page data never reaches the metadata function. Trace the value from the route record to the rendered head. Add a page-level value and retain a deliberate fallback only for missing images.
One route has no image tag The page data is missing, a child metadata object omitted the field, or a template condition suppressed the tag. Inspect the resolved record and final HTML. In Next.js, account for object replacement when a page defines its own Open Graph object.
The source page looks correct in a browser, but a crawler sees no tags Metadata is being added only after client-side rendering or crawler-specific output differs. Inspect the raw response HTML and render the metadata on the server or at build time. Verify the deployed response.
Preview image fails to load The URL is relative, private, expired, blocked, or points to a non-image response. Use an absolute public URL and open it without authentication. Check that the server returns the actual image.
Next.js build fails on the image convention The file exceeds the documented 8 MB limit or uses an unsupported format. Use JPG, JPEG, PNG, or GIF within the limit and check the documentation for your installed Next.js version.
Preview still shows the previous image after fixing HTML The consumer may have cached the URL or card data. First verify the deployed response and image URL; then use the relevant platform’s current preview refresh/debugging mechanism.
Image or title contains malformed markup Dynamic values were inserted without HTML attribute escaping. Use the framework’s safe metadata API or escape content for HTML attributes.

7. Performance, reliability, and cost

Metadata selection should use the page record already loaded for rendering where possible; avoid an extra per-request lookup solely for the social image when the same record contains it. For static builds, keep generated metadata in step with content updates. For generated Next.js images, account for documented caching defaults and avoid assuming they refresh at request time.

Reliability depends on the entire fetch path: the HTML must contain the right value, and the image host must serve the referenced asset publicly. A valid tag pointing to a private, missing, or inaccessible image still produces a broken preview. Keep a known-good fallback and check both the page response and image response during content publishing.

Open Graph tags themselves have no per-view fee. Your hosting, CMS, image storage, or image-generation services may have their own costs; the research material does not establish rates for them. No benchmark or platform-wide preview refresh behavior is assumed here.

8. Or skip the browser setup

If you need a rendered screenshot of each page to inspect its appearance while debugging metadata, ScreenshotNeo can capture a URL with one GET request. This is a separate visual check; it does not replace setting the page’s Open Graph tags. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/articles/example-page -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/articles/example-page"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/articles/example-page' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn about ScreenshotNeo, or sign up for 1,000 free screenshots a month with no card.

9. FAQ

Can a site use one fallback image and custom images on selected pages?

Yes. Resolve a page’s selected image first and use the fallback only when that field is absent or invalid.

Does og:image need to match the page’s visible hero image?

No requirement in the cited protocol says it must. Choose an image that represents the page and ensure it is intentionally associated with that page.

Should every page have its own image file?

No. Different pages can point to different assets, while related pages can intentionally share an image. The important part is that the metadata value is chosen for each page rather than hard-coded globally.

Will changing the HTML update every existing social preview immediately?

Not necessarily. The research confirms previews may be cached, but does not establish current refresh timing or controls for individual platforms. Verify the deployed metadata, then use the platform’s current refresh mechanism if needed.