Skip to content
Markdown API · Use case

Convert a JavaScript-rendered page or SPA to Markdown

Getting an SPA to Markdown means converting the DOM after JavaScript runs, not the HTML the server sent. A plain HTTP fetch of a React, Vue or Angular app returns an empty shell and a script tag, which is why docs portals, dashboards and storefronts come back blank. The Markdown API can render the page in a real browser first, wait for the content and convert what the user actually sees.

The problem

A fetched SPA is an empty shell, so the Markdown is empty too

Single-page applications ship a root div and load everything else at runtime. Convert the response body of a React app to Markdown and you get a title, maybe a noscript warning, and nothing else. The request succeeded, the content is missing, and nothing in the output tells you why.

Running your own headless browser fixes the render and creates a new problem: deciding when the page is ready. A fixed sleep is too short for the slow case and wasted time for the fast one. Network-idle heuristics never settle on pages that poll, and they fire too early on pages that load data after a click.

Microlink picks the fetch mode automatically with prerender set to auto. Force it to true for pages you know are client-rendered, add waitForSelector for the element that proves the data has arrived, and the conversion runs against the rendered DOM.

How it works

How to render an SPA before converting it to Markdown

Two options decide correctness: prerender chooses the browser, and the wait options decide when the DOM is ready to convert. The page preparation guide ranks the wait controls from the cheapest to the most forgiving.

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

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

const markdown = await microlink.markdown('https://app.example.com/docs', {
  prerender: true,
  waitForSelector: 'main h1'
})

prerender: true skips the plain fetch and spawns a headless browser. waitForSelector pauses until the heading appears, so the Markdown string you get back includes the hydrated content.

2 · Navigate fast, then wait for data
import createClient from 'microlink.io'

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

const cards = await microlink.markdown('https://app.example.com/catalog', {
  prerender: true,
  waitUntil: 'domcontentloaded',
  waitForSelector: '.product-card',
  selectorAll: '.product-card'
})

A quick lifecycle event plus a selector wait is the fastest reliable pattern when the default navigation signal is too slow. selectorAll resolves to an array with one Markdown string per card.

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

The Markdown arrives in the data.markdown field of the JSON response. Check the x-fetch-mode response header: prerender confirms that a browser render was used, and x-fetch-time reports how long the fetch step took.

Parameters used
  • prerender auto by default, true to force a headless browser render, false for a plain HTTP GET.
  • waitForSelector Pause the browser until an element matching the CSS selector appears.
  • waitUntil auto by default. Also load, domcontentloaded, networkidle0 or networkidle2.
  • waitForTimeout A fixed delay for pages with no stable selector. Capped by the plan timeout: 30 seconds free, 60 seconds Pro.
  • click Click one selector or several to open a tab or dismiss a dialog before the conversion.

When x-fetch-mode reports fetch, the page was served as static HTML and no browser was needed. prerender: auto makes that call for you and only renders when it must, which is the right default for a mixed list of URLs.

Why it works

Why a browser render comes before the Markdown conversion

The Markdown can only be as complete as the DOM it is converted from, and for a client-rendered app that DOM does not exist until the bundle has run.

01 · The real DOM
Conversion runs on what the browser rendered.
With prerender the page executes its JavaScript in a real browser, so hydrated components, fetched data and lazy sections are present in the DOM before the Markdown serializer reads it. Every request runs in its own isolated browser.

The html method returns the same rendered DOM as markup when you need to parse it yourself.

02 · Wait for the proof
A selector wait ends when the content exists.
Instead of a timer, wait for the element that only appears once the data has loaded. The request returns as early as possible and still covers the slow case, up to the plan timeout.

The same wait logic applies to screenshots of JavaScript-rendered pages and to metadata from single-page apps, so one selector per site serves all three.

03 · Auto when unsure
prerender: auto renders only when needed.
For a mixed set of URLs, leave prerender on auto: static pages are fetched with a plain request, client-rendered pages get the browser. Force true only for hosts you know.

When not to: server-rendered documentation and articles convert fine with a plain fetch, and forcing prerender there only adds latency. Once the page renders, scope the conversion to keep the app chrome out of the Markdown.

FAQ

Why does my Markdown come back empty for a React or Vue app?

The page is client-rendered and the conversion ran on the initial HTML, which is only a root element and script tags. Set prerender to true and add waitForSelector for an element that exists once the app has rendered.

How do I convert an SPA to Markdown with an API?

Send the URL with data.markdown.attr=markdown, prerender=true and a waitForSelector for the content container. Microlink renders the app in a headless browser, waits for that element and serializes the finished DOM as Markdown.

How do I know whether a browser render was used for the Markdown?

Read the x-fetch-mode response header: prerender means a browser rendered the page, fetch means a plain HTTP request was enough. x-fetch-time reports the time spent in that step.

How long can a Markdown conversion wait for a single-page app?

Up to the request timeout: 30 seconds on the free endpoint and 60 seconds on Pro plans. A longer waitForTimeout is ignored, so prefer waitForSelector, which returns as soon as the content appears.

Can I convert content to Markdown that only appears after a click?

Yes. Use click with the selector of the tab or button, then waitForSelector for the content it reveals, and the conversion includes it. Browser automation covers the other interactions available on a request.
Related use cases

Solve the next problem with the same API

Clean Markdown, no boilerplate

Convert only the article body: one selector keeps navigation, ads and widgets out of the Markdown.

Markdown from bot-protected pages

Convert pages behind Cloudflare, DataDome or Akamai: one option routes the request through the built-in proxy.

Bulk Markdown conversion with caching

Convert thousands of URLs in parallel, cached per URL and refreshed in the background for cheap re-indexing.

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.

Metadata from single-page apps

Render React, Vue or Angular apps in a headless browser, wait for the tags, then read the normalized metadata.

Ready to convert rendered apps?

Render first, wait for the data, then convert. Start on the free tier and turn your first single-page app into Markdown today.