Skip to content

Embed: Caching and performance

Embeds are read-mostly. The same URL renders the same card thousands of times. Lean into that — cache aggressively and serve from the edge.
This page covers embed-specific performance tactics. For the universal caching controls (ttl, staleTtl, force, and how to read cache headers), see caching patterns.

Embed-specific speedups

The single biggest win for embed-only requests is matching the response shape to your renderer. Three patterns:
  1. Static markup with direct embed — for <img> and <meta og:image>, return the asset itself. The CDN serves bytes directly with no JSON parsing in your stack.
    <img src="https://api.microlink.io?url=https://stripe.com&embed=image.url" />
  2. Filter the JSON when you only need a few fieldsfilter=title,description,image.url shrinks the payload and keeps your renderer's hot path tight.

    The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL with 'filter' API parameter:

    CLI Microlink API example

    microlink https://stripe.com&filter=title,description,image.url,logo.url

    cURL Microlink API example

    curl -G "https://api.microlink.io" \
      -d "url=https://stripe.com" \
      -d "filter=title,description,image.url,logo.url"

    JavaScript Microlink API example

    import createClient from 'microlink.io'
    
    const microlink = createClient()
    
    const data = await microlink.metadata('https://stripe.com')

    Python Microlink API example

    import requests
    
    url = "https://api.microlink.io/"
    
    querystring = {
        "url": "https://stripe.com",
        "filter": "title,description,image.url,logo.url"
    }
    
    response = requests.get(url, params=querystring)
    
    print(response.json())

    Ruby Microlink API example

    require 'uri'
    require 'net/http'
    
    base_url = "https://api.microlink.io/"
    
    params = {
      url: "https://stripe.com",
      filter: "title,description,image.url,logo.url"
    }
    
    uri = URI(base_url)
    uri.query = URI.encode_www_form(params)
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    
    request = Net::HTTP::Get.new(uri)
    response = http.request(request)
    
    puts response.body

    PHP Microlink API example

    <?php
    
    $baseUrl = "https://api.microlink.io/";
    
    $params = [
        "url" => "https://stripe.com",
        "filter" => "title,description,image.url,logo.url"
    ];
    
    $query = http_build_query($params);
    $url = $baseUrl . '?' . $query;
    
    $curl = curl_init();
    
    curl_setopt_array($curl, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET"
    ]);
    
    $response = curl_exec($curl);
    $err = curl_error($curl);
    
    curl_close($curl);
    
    if ($err) {
        echo "cURL Error #: " . $err;
    } else {
        echo $response;
    }

    Golang Microlink API example

    package main
    
    import (
        "fmt"
        "net/http"
        "net/url"
        "io"
    )
    
    func main() {
        baseURL := "https://api.microlink.io"
    
        u, err := url.Parse(baseURL)
        if err != nil {
            panic(err)
        }
        q := u.Query()
        q.Set("url", "https://stripe.com")
        q.Set("filter", "title,description,image.url,logo.url")
        u.RawQuery = q.Encode()
    
        req, err := http.NewRequest("GET", u.String(), nil)
        if err != nil {
            panic(err)
        }
    
        client := &http.Client{}
        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()
    
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            panic(err)
        }
    
        fmt.Println(string(body))
    }
  3. Disable metadata extraction when you only want a screenshot or iframemeta=false skips the metadata stage entirely.

    The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL with 'screenshot', 'meta' & 'embed' API parameters:

    CLI Microlink API example

    microlink https://stripe.com&screenshot&embed=screenshot.url

    cURL Microlink API example

    curl -G "https://api.microlink.io" \
      -d "url=https://stripe.com" \
      -d "screenshot=true" \
      -d "meta=false" \
      -d "embed=screenshot.url"

    JavaScript Microlink API example

    import createClient from 'microlink.io'
    
    const microlink = createClient()
    
    const data = await microlink.screenshot('https://stripe.com')

    Python Microlink API example

    import requests
    
    url = "https://api.microlink.io/"
    
    querystring = {
        "url": "https://stripe.com",
        "screenshot": "true",
        "meta": "false",
        "embed": "screenshot.url"
    }
    
    response = requests.get(url, params=querystring)
    
    print(response.json())

    Ruby Microlink API example

    require 'uri'
    require 'net/http'
    
    base_url = "https://api.microlink.io/"
    
    params = {
      url: "https://stripe.com",
      screenshot: "true",
      meta: "false",
      embed: "screenshot.url"
    }
    
    uri = URI(base_url)
    uri.query = URI.encode_www_form(params)
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    
    request = Net::HTTP::Get.new(uri)
    response = http.request(request)
    
    puts response.body

    PHP Microlink API example

    <?php
    
    $baseUrl = "https://api.microlink.io/";
    
    $params = [
        "url" => "https://stripe.com",
        "screenshot" => "true",
        "meta" => "false",
        "embed" => "screenshot.url"
    ];
    
    $query = http_build_query($params);
    $url = $baseUrl . '?' . $query;
    
    $curl = curl_init();
    
    curl_setopt_array($curl, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET"
    ]);
    
    $response = curl_exec($curl);
    $err = curl_error($curl);
    
    curl_close($curl);
    
    if ($err) {
        echo "cURL Error #: " . $err;
    } else {
        echo $response;
    }

    Golang Microlink API example

    package main
    
    import (
        "fmt"
        "net/http"
        "net/url"
        "io"
    )
    
    func main() {
        baseURL := "https://api.microlink.io"
    
        u, err := url.Parse(baseURL)
        if err != nil {
            panic(err)
        }
        q := u.Query()
        q.Set("url", "https://stripe.com")
        q.Set("screenshot", "true")
        q.Set("meta", "false")
        q.Set("embed", "screenshot.url")
        u.RawQuery = q.Encode()
    
        req, err := http.NewRequest("GET", u.String(), nil)
        if err != nil {
            panic(err)
        }
    
        client := &http.Client{}
        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()
    
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            panic(err)
        }
    
        fmt.Println(string(body))
    }
    Useful for OG images and dynamic banners — no metadata work, just the capture.

Cache strategy

For the controls that apply to every workflow — ttl, staleTtl, force, and how to verify caching through response headers — see caching patterns.
A recommended production setup for embeds:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://stripe.com' URL with 'ttl', 'staleTtl' & 'apiKey' API parameters:

CLI Microlink API example

microlink https://stripe.com&ttl=1d&staleTtl=0 --api-key YOUR_API_TOKEN

cURL Microlink API example

curl -G "https://pro.microlink.io" \
  -H "x-api-key: YOUR_API_TOKEN" \
  -d "url=https://stripe.com" \
  -d "ttl=1d" \
  -d "staleTtl=0"

JavaScript Microlink API example

import createClient from 'microlink.io'

const microlink = createClient({ apiKey: "YOUR_API_TOKEN" })

const data = await microlink.metadata('https://stripe.com', {
  ttl: "1d",
  staleTtl: 0
})

Python Microlink API example

import requests

url = "https://pro.microlink.io/"

querystring = {
    "url": "https://stripe.com",
    "ttl": "1d",
    "staleTtl": "0"
}

headers = {
    "x-api-key": "YOUR_API_TOKEN"
}

response = requests.get(url, params=querystring, headers=headers)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://pro.microlink.io/"

params = {
  url: "https://stripe.com",
  ttl: "1d",
  staleTtl: "0"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
request['x-api-key'] = "YOUR_API_TOKEN"
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://pro.microlink.io/";

$params = [
    "url" => "https://stripe.com",
    "ttl" => "1d",
    "staleTtl" => "0"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "x-api-key: YOUR_API_TOKEN"
    ]
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://pro.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://stripe.com")
    q.Set("ttl", "1d")
    q.Set("staleTtl", "0")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    req.Header.Set("x-api-key", "YOUR_API_TOKEN")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
Cache for a day, serve stale instantly while refreshing in the background. Requires a
PRO
plan.
For high-traffic embed surfaces — comment widgets, RSS readers, AI search results — pair staleTtl: 0 with a long ttl so users always hit cache and only the background refresh touches the origin.

Hot-linking and the embed URL

The embed=... URL is itself a CDN-cacheable asset. Browsers and downstream proxies (Slack, Discord, email clients) cache it by URL. Two consequences:
  • Stable URLs cache best. Avoid appending random query parameters or session IDs.
  • force=true defeats this. Use force only when you intentionally want a regeneration — never as a default.
If you need the URL to change when source content changes (cache busting), append a content hash you control:
<img src="https://api.microlink.io?url=https://your-site.com/post&screenshot&embed=screenshot.url&v=42" />
Bumping v invalidates downstream caches without touching Microlink's behavior.

Embed SDK lazy-loading

The Embed SDK uses IntersectionObserver and only fires its API call when each preview enters the viewport. Pages with hundreds of embedded links still hit a fast first paint. To preload above-the-fold cards, tighten the lazy options:
<Microlink url='...' lazy={{ rootMargin: '400px' }} />
The lazy prop is covered on the link preview component page.

When you don't need fresh data

Static sites and SSR builds often have the metadata at build time. Skip the runtime call:
<Microlink
  url='https://example.com/post'
  fetchData={false}
  setData={prefetchedData}
/>
That removes the API call entirely. Combined with build-time crawling, you ship zero-API-call embeds. See the link preview component page for setData.

Verify caching is working

Check the response headers when you suspect a slow embed:
HeaderWhat it tells you
x-cache-statusHIT (cache), MISS (fresh), BYPASS (forced)
x-cache-ttlEffective cache lifetime in milliseconds
x-response-timeA short value (under ~50ms) usually means a cache hit
For the full pattern, see verify caching behavior.

Next step

Learn how to embed authenticated dashboards and pages protected by antibot systems in private pages and proxy.