ScreenshotNeo

BlogHow-to

How to Add Open Graph Tags to a Next.js Website Hosted on Vercel in India

Add and verify Open Graph metadata in Next.js App Router on Vercel, including static and dynamic pages, route images, India-specific locale choices, and preview troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

Direct answer: In a Next.js App Router project, export a metadata object for fixed Open Graph values or implement generateMetadata for values that depend on route data. Deploy the app to Vercel, and use absolute public URLs for the page and its Open Graph image. Hosting in India does not require a different metadata API or tag syntax.

This guide uses the App Router (app/). The Pages Router uses a different approach, covered below. Next.js supports these metadata exports in Server Components; a route segment cannot export both metadata and generateMetadata. See the [Next.js Metadata API reference](https://nextjs.org/docs/app/api-reference/functions/generate-metadata).

1. Choose where the metadata belongs

Use the narrowest route segment that should own the values:

  • app/layout.tsx: shared site defaults such as the site name and default description.
  • app/page.tsx: values specific to the home page.
  • app/blog/[slug]/page.tsx: metadata generated from a particular article.
  • app/opengraph-image.jpg or a deeper route image file: a colocated image that Next.js uses to generate Open Graph image tags automatically.

Set fields explicitly when you want predictable previews: title, description, url, siteName, images, locale, and type. For an image specified in the openGraph object, use an absolute URL.

2. Add static Open Graph metadata

For a site or page whose share information is fixed, export metadata from a Server Component. This example sets defaults for the site in app/layout.tsx:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://www.example.in'),
  title: {
    default: 'Example India',
    template: '%s | Example India',
  },
  description: 'Guides and updates from Example India.',
  openGraph: {
    title: 'Example India',
    description: 'Guides and updates from Example India.',
    url: 'https://www.example.in',
    siteName: 'Example India',
    images: [
      {
        url: 'https://www.example.in/og/default.png',
        width: 1200,
        height: 630,
        alt: 'Example India website preview',
      },
    ],
    locale: 'en_IN',
    type: 'website',
  },
}

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en-IN">
      <body>{children}</body>
    </html>
  )
}

Replace the example domain and image with your deployed public URLs. The en_IN locale and lang="en-IN" above are examples for English content aimed at an Indian audience. Choose the locale and document language that match your actual content and audience; the fact that a deployment is hosted in India does not by itself determine either value.

metadataBase is useful when you use relative metadata URLs elsewhere. The example still uses an absolute Open Graph image and page URL to make the public destinations clear. If you place this object in a page instead, export it from that page’s Server Component.

3. Generate metadata from dynamic route data

For pages such as blog posts, return the share fields from generateMetadata. Fetch the same record your page uses, and build the URL from its canonical public slug rather than a preview deployment host.

import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getArticle } from '@/lib/articles'

type Props = {
  params: Promise<{ slug: string }>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const article = await getArticle(slug)

  if (!article) notFound()

  const url = `https://www.example.in/blog/${article.slug}`

  return {
    title: article.title,
    description: article.summary,
    alternates: { canonical: url },
    openGraph: {
      title: article.title,
      description: article.summary,
      url,
      siteName: 'Example India',
      images: [
        {
          url: article.ogImageUrl,
          alt: article.ogImageAlt,
        },
      ],
      locale: 'en_IN',
      type: 'article',
    },
  }
}

export default async function ArticlePage({ params }: Props) {
  const { slug } = await params
  const article = await getArticle(slug)
  if (!article) notFound()
  return <article><h1>{article.title}</h1>{article.content}</article>
}

getArticle is an application-specific function: implement it against your CMS or data store and ensure it returns a valid public image URL. The promise-based params signature is used in current Next.js documentation examples; check the API for the Next.js version installed in your project, since older versions may type route parameters differently.

Next.js documents that matching fetch requests in generateMetadata, layouts, pages, and Server Components are memoized. Reuse the same request where possible. If your data library does not use fetch, apply its supported caching strategy deliberately.

4. Add an Open Graph image with the file convention

For a default image, put a supported image file such as opengraph-image.jpg in app/. For a blog-specific default, place it in app/blog/; a more deeply nested route image takes precedence over an image in a parent segment. Next.js generates the corresponding metadata tags from the file.

The documented file convention supports JPG, JPEG, PNG, and GIF, with an 8 MB maximum. A file larger than that limit causes the build to fail. See [Next.js Open Graph image file conventions](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image).

For an image generated in code, create opengraph-image.tsx in the route segment and return an image response. Next.js recommends ImageResponse from next/og as the straightforward option. Export alt, size, and contentType where appropriate. Generated images are statically optimized and cached by default unless Dynamic APIs or uncached data make them dynamic.

Use the file convention for a fixed or colocated image. Use an explicit metadata image URL or a generated image when the image varies with page data. Avoid specifying conflicting defaults in several places without checking which route should win.

5. Account for nested routes and metadata replacement

Metadata fields are composed across route segments, but a child route that defines its own openGraph object replaces the parent’s openGraph object. It does not automatically merge each nested Open Graph field. If a child needs shared values, include them again:

import type { Metadata } from 'next'

const sharedOpenGraph = {
  siteName: 'Example India',
  locale: 'en_IN',
  type: 'website' as const,
  images: ['https://www.example.in/og/default.png'],
}

export const metadata: Metadata = {
  openGraph: {
    ...sharedOpenGraph,
    title: 'About Example India',
    description: 'About the site and its publishing team.',
    url: 'https://www.example.in/about',
  },
}

Adjust type for the page. File-based metadata has higher priority than config-based metadata. If an image or field does not match the object you expect, inspect the route’s metadata files and every parent and child metadata export.

6. Deploy and validate on Vercel

  1. Set the production domain for the project in Vercel and deploy the app.
  2. Use that public production hostname in canonical and Open Graph page URLs, and make sure every image URL resolves publicly. This is practical guidance for shareable public URLs; the reviewed Next.js documentation does not define a Vercel-specific Open Graph configuration.
  3. Open the deployed page’s HTML source or inspect its document head. Confirm the rendered og:title, og:description, og:url, and og:image match that page.
  4. Check that the image can be fetched without a session, local-network access, or a temporary preview URL.
  5. Use the relevant social platform’s current preview or debugging tool to inspect the production URL before publishing. Platforms may use their own crawlers and caches; this guide does not establish their current refresh rules.

Dynamic metadata can be streamed after the initial UI on dynamic routes. Next.js says HTML-limited bots such as facebookexternalhit continue to wait for metadata, while metadata for other dynamic pages may stream after the initial UI. If a particular preview is missing metadata, check the rendered response for that crawler and test through the platform’s current preview tool.

7. If you use the Pages Router

The examples above use the App Router’s Metadata API. In a Pages Router project, add the tags with Next.js’s Head component from next/head, and provide values for each page. Do not expect an App Router metadata export to configure a Pages Router page. If you are migrating, first identify which router owns the route in question.

8. Troubleshooting

Symptom Likely cause Fix
Preview shows no image The image URL is relative, private, invalid, or unavailable to the crawler. Use an absolute public URL in openGraph.images, verify the deployed image path, and inspect the generated og:image value.
A child page loses the parent image or description The child defines an openGraph object, replacing the parent’s object. Spread or restate the shared Open Graph fields in the child metadata.
Metadata export fails or is ignored The export is in a Client Component, or the route segment exports both metadata and generateMetadata. Put metadata exports in a Server Component and choose one export per segment.
The build fails after adding an image file The Open Graph image exceeds the documented 8 MB file limit, or its file convention is unsupported. Reduce the file size and use a supported JPG, JPEG, PNG, or GIF file.
A preview shows another route’s title or image A parent default, child override, or deeper file-based metadata is taking precedence. Inspect the route tree and final rendered head; resolve the metadata at the segment that owns the page.
Dynamic page has no metadata for a crawler Metadata depends on data that fails, is missing, or takes too long to resolve; crawler rendering behavior may differ. Ensure the route data lookup succeeds for the production slug, handle missing records, and inspect the response with the relevant platform preview tool.
Preview still appears stale after a correction The platform may be showing cached preview data. Confirm the current production HTML and image first, then use the platform’s current refresh or preview debugging flow. Cache behavior differs by platform.
URL points to a preview deployment or localhost The metadata was built from a local or environment-specific host. Use the canonical production hostname for public page and image URLs.

9. Performance, reliability, and cost

  • Performance: Keep metadata generation focused. Reuse matching Next.js fetch calls where possible; Next.js documents memoization across metadata and page rendering. Choose static metadata when values do not depend on route data.
  • Reliability: Make metadata data sources available during rendering, handle missing records, and ensure image URLs remain public and stable. A valid tag cannot compensate for an inaccessible image.
  • Image delivery: Keep file-convention images within the documented size limit. Prefer a correctly sized social preview asset and meaningful alt text. Avoid making a share image depend on a short-lived URL.
  • Cost: The Metadata API and file conventions are framework features; this implementation guide does not establish a separate fee for adding tags. Hosting, data fetching, and any image-generation or storage services have their own terms and costs, which should be checked with their providers.

10. Inspect the deployed result with ScreenshotNeo

A screenshot is a useful visual check of the deployed page, but it does not replace inspecting the actual metadata tags or using a social platform’s preview tool. [ScreenshotNeo](https://screenshotneo.com) can capture the production URL so you can review the page as rendered. Its screenshot API also returns page verdict and billing headers.

Or skip the browser setup

Call the API with your production URL. See the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/) for the request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.example.in/blog/my-post -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://www.example.in/blog/my-post"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://www.example.in/blog/my-post',
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`)
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()))

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. [Create a free ScreenshotNeo account](https://screenshotneo.com/account/sign-up/).

FAQ

Does hosting the Next.js site in India change the Open Graph API?

No India-specific syntax is documented in the reviewed Next.js references. Use the same App Router Metadata API and choose locale and language for the page’s audience.

Should every route have its own Open Graph image?

Only when a route needs a distinct preview. A site-wide or section-level file can provide a default; route-specific metadata or a deeper image file can provide a more specific image.

Can I use a client component to export metadata?

No. Next.js documents metadata and generateMetadata exports as Server Component features.

Will changing the tags immediately update every social preview?

Not necessarily. Verify the deployed HTML first, then check the platform’s current preview tooling because crawler and cache behavior is platform-specific.

Primary references: [Next.js generateMetadata](https://nextjs.org/docs/app/api-reference/functions/generate-metadata), [Next.js Open Graph image conventions](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image), and [Next.js Metadata and OG images guide](https://nextjs.org/docs/app/getting-started/metadata-and-og-images).