Home / Blog / Extending Vue Router Links in Vue 3
Extending Vue Router Links in Vue 3

Extending Vue Router Links in Vue 3

Daniel Kelly
Daniel Kelly
April 23rd 2021

The <router-link> tag is a great tool for navigating between different pages of your Vue application but it is not the tool to use when navigating to an external link, for that you’d want to use a regular <a> tag. Maybe it's just me, but often times, I can't be bothered with keeping up with the difference. Other times the links might be dynamic, that is, coming from a database or some user provided data source. In such cases you simply won’t know if the link is external or internal and what a pain it would be to do a v-if manually in every place that link might be used.

Wouldn't it be nice to just use a single component for all the links internal and external? If you're anything like me you'll be doing this right about now.

https://media.giphy.com/media/fH64u5y85GaibsUOvQ/giphy.gif

Thankfully extending the <router-link> component is super simple by wrapping it in your own custom component. Let’s do it! Let’s build an AppLink component that will handle any link, external OR internal.

The AppLink Component

The first thing we should do is allow our AppLink component to take all the same props that the router-link takes. Why? So that the "interface" for our component can mimick that of Router Link and we won't have yet another API to remember. We can do this by importing RouterLink from Vue Router and spreading it’s props into the props option of our component.

// AppLink.vue
<script>
import {RouterLink} from 'vue-router'
export default{
  props:{ ...RouterLink.props }
}
</script>

In the template area we can now create a router link tag and just bind all the props from our component to it. We also need to pass in the slot so that text and markup provided between the tags will appear in the router-link.

// AppLink.vue
<template>
  <router-link v-bind="$props"><slot /></router-link>
</template>

As is right now, we’ve handled all internal links. What about external links? As mentioned previously, external links use a tags, so let’s add that to our template. Like router link, we should pass the slot. Let's also bind the href to the to property.

// AppLink.vue
<template>
  <a :href="to"><slot/></a>
  <router-link v-bind="$props"><slot/></router-link>
</template>

Cool, this accounts for both internal links and external links! (It may be worth noting at this point, the above only works with Vue 3 as it contains more than 1 root element).

Now, we just need a condition to tell us what kind of link was provided to the AppLink. We can create a computed property called isExternal to determine this. First, we’ll check that the value of our to prop is a string. This is necessary because the to prop might be an object such as sometimes passed to router-link (ie. :to="{name:'RouteNameHere'}"). Then we’ll check if the string starts with a string of http. If both these conditions are true, then we have ourselves an external link.

// AppLink.vue
<script>
export default{
   //...
  computed:{
    isExternal(){
      return typeof this.to === 'string' && this.to.startsWith('http')
    }
  }
}
</script>

In the template area, we can now use the isExternal computed prop in a v-if to show the a tag when it’s true otherwise show the router-link.

// AppLink.vue
<template>
  <a v-if="isExternal" :href="to"><slot/></a>
  <router-link v-else v-bind="$props"><slot/></router-link>
</template>

And that's it! We're done! After registering the component globally with your app you can now use it like so:

// Anywhere in your app
<AppLink :to="[external-or-internal-link]">Click Me</AppLink>
Extending Router Links Demo GIF

Further Flexibility

Open in New Tab

Let's make the AppLink component even more useful. Let's say we wanted all our external links to always open in a new tab. Easy. Just add a target="_blank" to the <a> tag in the component and all external links throughout the site now open in a new tab.

// AppLink.vue
<template>
  <a ... target="_blank"><slot/></a>
  ...
</template>

This is the rule you probably want to apply to most external links on your site but should you want any particular external link to open in the same tab you can just tell that link instance to do so with the html target attribute.

<AppLink :to="https://vueschool.io" target="_self">Vue School</AppLink>

Link Security

When you link to a page on another site using the target="_blank" attribute, you can end up exposing your site to performance and security issues:

  • The page linked to can end up running on the same process as your page. Depending on what's going on in the linked to page, this could slow your own page down.
  • The other page also has access to the original pages window via the window.opener property causing security concern.

For more details on this see this informative post.

The fix for this is providing a rel="noopener" attribute to all of your external link tags. But what a pain to have to remember to do so... oh wait... we don't have to. We can add it once to our AppLink component and be done.

// AppLink.vue
<template>
  <a ... rel="noopener"><slot/></a>
  ...
</template>

Unique Styling for External Link

I've seen some websites style external links on their website a little differently than links that go to other places on their own site. This can help users better understand how they ended up on a site they didn't originally visit. It could be anything from a subtle "external link" icon next to the link to a little warning below the link saying something like "Links to Third-Party Website". Making this work with our component would be as easy as adding an external-link class to the a tag in our template and then using css to style it differently or to add in an :after sudo-element. You can even add brand new elements to just the external link like a font awesome icon.

// AppLink.vue
// (must have font awesome font included in project)
<template>
  <a ... class="external-link">
    <slot/> <i class="fas fa-external-link-alt"></i>
  </a>
  ...
</template>

<style scoped>
.external-link i {
  font-size: 0.8em;
  opacity: 0.7;
}
</style>
External LInk Styled

Conclusion

This is just a taste of what you can do to extend router-link to accommodate common and case specific needs. Also, with all your links encapsulated in a single component you can easily update different aspects of all your links down the road. Can you think of any other useful ways to improve our AppLink component? Do you use a similar approach in your apps and have some wisdom to share? Share your tips and tricks on extending Vue Router links in the comments below!

To learn more about Vue Router 4 checkout our FREE Vue Router 4 course! We've even got a lesson dedicated to just this topic Extending Router Link for External URLs!

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

7 Beautiful Next-Level Button Components with Vue, VueUse, and TailwindCSS

7 Beautiful Next-Level Button Components with Vue, VueUse, and TailwindCSS

Combine Vue, VueUse, and TailwindCSS for some practical and beautiful buttons
Daniel Kelly
Daniel Kelly
Unlocking the Power of AI in Your Vue Coding Workflow

Unlocking the Power of AI in Your Vue Coding Workflow

Explore how AI is transforming software development, acting as a valuable coding companion to streamline workflows and overcome challenges. This article delves into practical strategies for effectively integrating AI tools, offering insights for developers at any skill level.
David Robertson
David Robertson

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.