Link previews are a fantastic way to keep readers engaged on your blog. When a user hovers over a link, showing a snapshot of the destination page helps them decide whether to click, without losing their current reading context. In this guide, we will build a robust link preview system for Next.js.
Why Playwright and the Popover API?
Traditionally, link previews required either heavy third-party iframe overlays or client-side screenshots generated on-the-fly, which are slow and tax the client's network. By using Playwright, we can capture screenshots of destination links at build time (during static generation). By pairing this with the native HTML Popover API, we get hardware-accelerated animations and top-layer rendering for free without needing a complex state manager or library.
Step 1: Setting up Playwright
First, we install Playwright as a dev dependency to automate screenshot generation during our build process.
npm install -D playwrightNext, we write a node script that takes a list of URLs, opens a headless browser, and snaps screenshots into our public/previews directory:
// scripts/generate-previews.js
const { chromium } = require('playwright');
const fs = require('fs');
async function run() {
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1200, height: 630 }
});
const urls = ['https://wikipedia.org', 'https://github.com'];
for (const url of urls) {
const slug = url.replace(/https?:\/\/(www\.)?/, '').replace(/\//g, '-');
await page.goto(url);
await page.screenshot({ path: `public/previews/${slug}.png` });
}
await browser.close();
}
run();Step 2: Using the Native Popover API
The Popover API allows elements to be promoted to the top layer automatically. We define our link and popover container in JSX. Notice the popovertarget attribute on our link:
export function PreviewLink({ href, slug, children }) {
return (
<>
<a href={href} popovertarget={'preview-' + slug} className="preview-trigger">
{children}
</a>
<div id={'preview-' + slug} popover="auto" className="preview-popover">
<img src={'/previews/' + slug + '.png'} alt="Link preview" />
</div>
</>
);
}Step 3: Polishing with CSS
Now, let's style the popover to animate smoothly when transitioning into visibility. We can use the :popover-open pseudo-class:
.preview-popover {
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1);
overflow: hidden;
max-width: 320px;
transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
opacity: 0;
transform: translateY(10px);
}
.preview-popover:popover-open {
opacity: 1;
transform: translateY(0);
}
@starting-style {
.preview-popover:popover-open {
opacity: 0;
transform: translateY(10px);
}
}With this simple layout, you have a modern, high-performance link preview feature that feels extremely native and lightning fast!
