Skip to content
Metadata API · Use case

Extract metadata from single-page apps and client-rendered pages

To extract metadata from single-page apps, the page has to run first. Frameworks that set document.title and the meta tags at runtime leave the initial HTML empty, so crawlers that only fetch see the app name and no image. The Metadata API can render the page in a headless browser and read the tags the app actually produces.

The problem

Single-page apps set their metadata after the HTML is served

React Helmet, Vue Meta and their equivalents write title, description and og:image on the client. A fetch-based extractor reads the server response, which contains only the shell, and reports the same app name and the same generic description for every route.

The textbook fix is server-side rendering, or a prerendering service in front of the app, and that is a project for the site’s owner. When you are unfurling links to other people’s apps, you cannot change how they render. Sleeping for a fixed time before reading the DOM is the other common hack, and it is too short on slow days and wasted time on fast ones.

Microlink picks the fetch mode on its own with prerender set to auto. For pages you know are client-rendered, set prerender to true and add waitForSelector for an element that only exists once the route has rendered. The metadata is then read from the live DOM and normalized like any other page.

How it works

How to prerender a page before reading its metadata

prerender decides whether a browser is used, and the wait options decide when the tags are trustworthy. The page preparation guide has the full decision table.

1 · Force the browser
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const { title, description, image } = await microlink.metadata(
  'https://app.example.com/items/42',
  { prerender: true, waitForSelector: 'main h1' }
)

The browser executes the app’s JavaScript, the route renders its h1, and only then is the metadata read. You get the same normalized fields as for a static page.

2 · Wait for the tag itself
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const { image } = await microlink.metadata('https://app.example.com/items/42', {
  prerender: true,
  waitForSelector: 'meta[property="og:image"]'
})

When the app injects the tag late, wait for the tag rather than for visible content. Any CSS selector works, including attribute selectors on meta elements.

3 · The same request as a URL
curl 'https://api.microlink.io/?url=https%3A%2F%2Fapp.example.com%2Fitems%2F42&prerender=true&waitForSelector=main+h1'

The x-fetch-mode response header reports prerender when a browser was used and fetch when a plain request was enough. x-fetch-time shows how long that step took.

Parameters used
  • prerender auto (default) lets the service decide, true forces a headless browser, false forces a plain HTTP GET.
  • waitForSelector Pauses until the CSS selector appears in the DOM. Works for meta tags too.
  • waitUntil Lifecycle event that marks navigation as done: auto (default), load, domcontentloaded, networkidle0 or networkidle2.
  • waitForTimeout A fixed wait, as a last resort when the page has no stable selector.
  • meta Restrict detection to the fields you need to keep renders fast.
  • ttl Cache the rendered result for up to 31 days; browser renders are slower than fetches. Pro plans.

Rendered requests take longer than plain fetches, so keep meta scoped to the fields you use and let the cache serve the repeats. When nothing else works, navigate with waitUntil set to domcontentloaded and then wait for the element you need, as the waitUntil reference suggests.

Why it works

Why a headless browser is the only source for SPA Open Graph tags

If the tags are produced by code, only executing that code reveals them.

01 · The tags the app writes
Metadata read from the live document.
After the render, document.title, the meta tags and JSON-LD reflect the route a visitor would see. Microlink normalizes those exactly as it does for a server-rendered page, so your preview code does not branch per site.

Combine with override rules when the app still gets a field wrong after rendering.

02 · Auto by default
prerender: auto renders only when it must.
For mixed sources, leave prerender on auto: static pages get a plain HTTP GET and client-rendered ones get the browser. Force true only for hosts you know need it, and false for hosts you know do not.

x-fetch-mode tells you which path was taken, so you can build a per-domain setting from your logs.

03 · Deterministic waits
Wait for the tag, not for a timer.
A selector wait on the meta tag returns the moment the app sets it and still covers slow networks. Fixed delays are either wasteful or unreliable.

When not to: if the site serves correct tags in its HTML, a plain fetch is faster and prerender adds nothing. The same render-then-read approach applies to Markdown from JavaScript-rendered pages and screenshots of dynamic content.

FAQ

Why does the metadata show the app name instead of the page title?

The title is set by JavaScript after load, and the extraction read the initial HTML. Set prerender to true and wait for an element or tag that only exists once the route has rendered. The metadata troubleshooting guide covers the other causes of wrong fields.

How do I get metadata from a React app that sets its tags with JavaScript?

Request the URL with prerender: true so the page runs in a headless browser, and add waitForSelector for an element or a meta tag that appears after the route renders. The title, description and image are then read from the live document. The same request works for Vue, Angular, Svelte and other client-rendered frameworks.

Does prerender slow down metadata extraction from single-page apps?

A browser render takes longer than a plain fetch, because the browser waits for page events before reading the DOM. Keep prerender on auto for mixed sources, scope meta to the fields you use, and let the 24-hour cache serve the repeats.

Can the metadata request wait for the og:image tag specifically?

Yes. waitForSelector accepts any CSS selector, including meta[property="og:image"], so the extraction waits until the app injects the tag. A fixed waitForTimeout also works, but it cannot exceed the request timeout of 30 seconds on the free plan and 60 seconds on Pro.

How do I confirm a browser was used to extract the metadata?

Read the x-fetch-mode response header: prerender means the page was rendered in a browser, fetch means a plain request was enough. x-fetch-time reports how long the fetch step took.
Related use cases

Solve the next problem with the same API

Fix missing or wrong og:image

Override any normalized field with a rule and chain fallbacks so link previews never render empty.

Only the fields you need

Include or exclude normalized fields per request and trim the JSON for faster, lighter metadata calls.

Custom fields alongside metadata

Get prices, ratings, headings or any CSS selector, typed and returned next to the normalized metadata.

Markdown from JavaScript-rendered pages

Render single-page apps in a real browser, wait for the content, then convert the finished DOM to Markdown.

Screenshots of JavaScript-rendered pages

Wait for a selector, a lifecycle event or a delay so single-page apps and lazy sections finish rendering before capture.

PDFs of JavaScript-rendered pages

Print dashboards and single-page apps after they render: wait for the chart, open tabs and sections, then print.

Ready to read client-rendered metadata?

Render first, then extract. Start on the free tier and get the right title and image from your first single-page app today.