Skip to content

Advanced Features

This page covers the advanced features of astro-smart-links: custom link structures, link transforms and a complete configuration. Every preview is the real output of that config.

Fully control the link HTML with wrapperTemplate. The callback receives (node, type, meta) and returns the replacement node; meta.className holds the configured class for the link type:

Custom Link Structure

Internal Link

External Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
smartLinks({
wrapperTemplate: (node, type, meta) => {
const icons = {
internal: ['after', '📄'],
external: ['after', '🔗'],
broken: ['before', '⚠️'],
};
const [position, icon] = icons[type];
if (position === 'before') {
node.children.unshift(h('span', { className: 'mr-1' }, icon));
} else {
node.children.push(h('span', { className: 'ml-1' }, icon));
}
node.properties.className = [
...(node.properties.className || []),
meta.className,
'flex',
'items-center',
'gap-1',
];
return node;
},
}),

Append a label to make the link type obvious:

Adding Badges

Internal Link

External Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
import { h } from 'hastscript';
smartLinks({
wrapperTemplate: (node, type, meta) => {
const badges = {
internal: ['badge badge-primary badge-sm', 'Internal'],
external: ['badge badge-secondary badge-sm', 'External'],
broken: ['badge badge-error badge-sm', 'Missing'],
};
const [className, label] = badges[type];
node.children.push(h('span', { className }, label));
node.properties.className = [
...(node.properties.className || []),
meta.className,
'flex',
'items-center',
'gap-2',
];
return node;
},
}),

customInternalLinkTransform, customExternalLinkTransform and customBrokenLinkTransform modify nodes per link type. Each callback receives (node, meta). Custom transforms take over the link entirely, so apply meta.className yourself:

Link Transforms

Internal Link

External Link

Broken Link

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
smartLinks({
customInternalLinkTransform: (node, meta) => {
node.properties.className = [...(node.properties.className || []), meta.className];
if (meta.pathname?.includes('demo')) {
node.properties['data-section'] = 'demos';
}
},
customExternalLinkTransform: (node, meta) => {
node.properties.className = [...(node.properties.className || []), meta.className];
node.properties.target = '_blank';
node.properties.rel = 'noopener noreferrer';
node.properties['data-external'] = 'true';
if (meta.href.includes('github.com')) {
node.properties.className.push('github-link');
}
},
customBrokenLinkTransform: (node, meta) => {
node.properties.className = [...(node.properties.className || []), meta.className];
node.properties['data-error'] = 'true';
node.properties.title = 'This page does not exist';
},
}),

Links matched by ignore are left completely untouched: no classes, no target/rel, no icon, and they are excluded from broken-link checks. All other links are processed as usual:

Ignoring Links

astro.config.mjs
import { smartLinks } from 'astro-smart-links';
smartLinks({
// RegExp patterns match both locales, e.g. /draft/ and /en/draft/
ignore: [/\/draft\//, /\/preview\//],
}),

A complete example combining the most common options:

Complete Configuration

Internal Link

External Link

Broken Link

astro.config.mjs
import { defineConfig } from 'astro/config';
import { smartLinks } from 'astro-smart-links';
export default defineConfig({
integrations: [
smartLinks({
internalLinkClass: 'internal-link',
externalLinkClass: 'external-link',
brokenLinkClass: 'broken-link',
target: '_blank',
rel: 'noopener noreferrer',
ignore: ['/draft/'],
// Broken link handling
failOnBroken: true,
reportFile: '.smart-links-report.json',
}),
],
});

Keep exploring: