Notion Mention Link
A Notion-inspired rich link mention with a securely fetched URL preview on hover.
Installation
File Structure
The registry installs the client component, the protected metadata fetcher, and the API route it uses.
Usage
import { NotionMentionLink } from "@/components/unlumen-ui/notion-mention-link";
export default function Example() {
return <NotionMentionLink url="desengs.com" />;
}The mention fetches its title, description, image, favicon, and site name automatically. The protocol is optional, so both desengs.com and https://desengs.com work. Hover it or focus it with the keyboard to reveal the preview card.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
url | string | - | Website URL to display and preview. The https:// protocol is optional. |
endpoint? | string | "/api/notion-mention-link" | Same-origin endpoint used for metadata and proxied preview assets. |
metadata? | NotionMentionLinkMetadata | - | Optional trusted metadata that skips the metadata request. |
prefetch? | "mount" | "hover" | "mount" | Fetch metadata on mount or wait until the first hover/focus. |
openDelay? | number | 180 | Delay in milliseconds before the preview opens. |
closeDelay? | number | 120 | Delay in milliseconds before the preview closes. |
previewWidth? | number | 360 | Preview card width in pixels, capped to the viewport width. |
previewHeight? | number | 160 | Preview media height in pixels. No space is reserved when the page has no image. |
align? | "start" | "end" | "start" | Aligns the preview to the start or end edge of the mention. |
showPreview? | boolean | true | Controls whether the hover/focus preview can be displayed. |
invalidLabel? | string | "Enter a valid website URL." | Message displayed instead of a mention when the website URL is malformed. |
previewClassName? | string | - | Additional classes applied to the positioned preview wrapper. |
className? | string | - | Additional classes applied to the mention link. |
Notes
- Security: the installed Node.js route only accepts HTTP(S), rejects credentials and non-standard ports, resolves every hostname, blocks loopback/private/link-local addresses, pins requests to a validated address, and revalidates every redirect.
- Resource limits: requests time out after 5 seconds, HTML is limited to 512 KB, images to 5 MB, redirects to 3 hops, and the included route limits each client to 60 requests per minute per server instance.
- Privacy: preview images and favicons are served through the same protected endpoint, so the visitor's browser does not contact those third-party asset hosts directly.
- Missing images: when a page has no preview image, or its image cannot be loaded, the card renders without a media area.
- Invalid URLs: malformed values render a compact validation message and never trigger a network request.
- Animation: the preview uses a short fade, scale, and vertical transition when it opens or closes.
- Caching: metadata and images use shared cache headers. The client also deduplicates metadata for repeated mentions of the same URL.
- Deployment: the included route uses the Node.js runtime because DNS validation and address pinning are not available in the Edge runtime.
- Production scaling: the built-in limiter is intentionally dependency-free. For a multi-instance deployment, add a shared platform rate limit in front of the route.
Credits
Built by leo.
Inspired by Notion's link mention interaction.
Keep in mind
Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.