Skip to content

Instantly share code, notes, and snippets.

@Anoesj
Last active October 2, 2024 23:14
Show Gist options
  • Save Anoesj/71a445cb95bfffbbb71850d6b6d0f3c4 to your computer and use it in GitHub Desktop.
Save Anoesj/71a445cb95bfffbbb71850d6b6d0f3c4 to your computer and use it in GitHub Desktop.
Nuxt v-html but an <a> navigates using the router, simulating <NuxtLink>
/*
This registers a v-nuxt-html directive, that can be used as a drop-in replacement for v-html.
It makes sure all rendered <a> tags navigate using the router, simulating the behavior of <NuxtLink>.
Inspired by https://www.trpkovski.com/2024/03/24/is-it-possible-to-use-nuxt-link-in-content-rendered-with-v-html
Improved so it renders the HTML on the server side as well and doesn't
`event.preventDefault` the click in some situations.
*/
export default defineNuxtPlugin((nuxtApp) => {
function handleLinkClick (event: MouseEvent) {
const a = event.target as HTMLAnchorElement;
const href = a.getAttribute('href');
const target = a.getAttribute('target');
/*
Alt key actually triggers a download of the link, but not in
every browser and it's a useless feature anyway, so we can
event.preventDefault() it without feeling guilty :-)
*/
const isShortcutKeyPressed =
// In new tab (Windows/Linux)
event.ctrlKey
// In new window (not sure if in all browsers and OS's or just some though)
|| event.shiftKey
// In new tab (Mac)
|| event.metaKey;
if (href && target !== '_blank' && !isShortcutKeyPressed) {
if (href.startsWith('/')) {
event.preventDefault();
useNuxtApp().$router.push(href);
}
else if (href.startsWith(window.location.origin)) {
event.preventDefault();
useNuxtApp().$router.push(href.replace(window.location.origin, ''));
}
}
}
function addListeners (el: HTMLElement) {
if (import.meta.client) {
for (const a of el.querySelectorAll('a')) {
a.addEventListener('click', handleLinkClick, { passive: false });
}
}
}
function removeListeners (el: HTMLElement) {
if (import.meta.client) {
for (const a of el.querySelectorAll('a')) {
a.removeEventListener('click', handleLinkClick);
}
}
}
nuxtApp.vueApp.directive<HTMLElement, string>('nuxtHtml', {
mounted (el, binding) {
el.innerHTML = binding.value;
addListeners(el);
},
beforeUpdate (el) {
removeListeners(el);
},
updated (el, binding) {
el.innerHTML = binding.value;
addListeners(el);
},
beforeUnmount (el) {
removeListeners(el);
},
getSSRProps (binding) {
// This makes sure during SSR the `innerHTML` property of the element is set to the bindng (the html string).
// During SSR, all hooks above don't run at all: https://vuejs.org/guide/scaling-up/ssr.html#custom-directives
return {
innerHTML: binding.value,
};
},
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment