nuxt / framework

Old repo of Nuxt 3 framework, now on nuxt/nuxt
https://nuxt.com
10.63k stars 1.05k forks source link

`NuxtLink` component #2073

Closed pi0 closed 2 years ago

pi0 commented 2 years ago

Nuxt 2 has a build-in NuxtLink that is basically a wrapper over <RouterLink> with some improvements like page smart prefetching. We shall support t for Nuxt 3 as a built-in app component:

Some more ideas to discover by @lihbr: https://lihbr.com/blog/one-link-component-to-rule-them-all

lihbr commented 2 years ago

Awesome!

From my understanding, Nuxt 3 already provides a <NuxtLink /> component that is just a renamed <RouterLink />: https://github.com/nuxt/framework/blob/main/packages/nuxt3/src/pages/runtime/router.ts#L19

I don't have much knowledge about prefetching Nuxt payloads as I never had the opportunity to dive into Nuxt 2 <NuxtLink /> internals. However I do have some experience building "agnostic" link components, like the one pictured in my blog post, or more recently the one created for @prismicio/vue@next targeting Vue 3: https://github.com/prismicio/prismic-vue/blob/v3/src/components/PrismicLink.ts#L133-L266

The latter might be of some help/inspiration while creating Nuxt 3 <NuxtLink /> component. In that regard, I'm happy to help, or even provide a first draft PR or spec that implements the "agnostic" part of such component that I'm familiar with!

danielroe commented 2 years ago

@lihbr That would be great! That agnostic behaviour is exactly what we'd like to include in NuxtLink 👍

pi0 commented 2 years ago

I second this. Indeed the goal is to make NuxtLink an agnostic component that optionally supports Router navigations

lihbr commented 2 years ago

Here's a first naive/draft specs for it, take it mainly as a bundle of ideas regarding the new <NuxtLink> component. I guess we can work from here and add the prefetching part to it. (feel free to move it to a more convenient place also)

Labels are going as follows:

Overview

This document presents the specifications (specs) of a <NuxtLink> component for Nuxt 3. It reimplements most Nuxt 2 <NuxtLink> component features while also allowing the new component to also act as a drop-in replacement for HTML anchor (<a>) tag, effectively making the new <NuxtLink> component agnostic regarding the type of links (external or internal) it handles.

Background Information

Nuxt 2 provides a link component for internal links that extends Vue Router's <RouterLink> component. On top of that, it also handles smart prefetching of links as they become available in the viewport, assuming user's connection is fast enough for that feature (not offline nor 2g).

As of today, Nuxt 3 link component is just a reexport of Vue Router's <RouterLink> component, with no extends of any sort on top of it.

Specifications

The following describes how Nuxt 3 <NuxtLink> component could work.

Interfaces

Props

The <NuxtLink> component accepts the following props (interface):

type NuxtLinkProps = {
    // Routing
    to?: string;
    href?: string;

    // Attributes
    blank?: boolean;
    target?: string;
    rel?: string;

    // Prefetching
    prefetch?: boolean;
    noPrefetch?: boolean;

    // Styling
    activeClass?: string;
    exactActiveClass?: string;
    prefetchedClass?: string;

    // Vue Router's `<RouterLink>` additional props
    replace?: boolean;
    ariaCurrentValue?: string;

    // Edge cases handling
    external?: boolean;
    internal?: boolean;
};

Nuxt configuration

The following props can be configured globally inside Nuxt config router object. They will be used with a lower priority than direct props by <NuxtLink> components:

import { defineNuxtConfig } from "nuxt3";

export default defineNuxtConfig({
    router: {
        prefetchLinks: false, // defaults to `true`
        linkExternalRelAttribute: "noopener", // defaults to `"noopener noreferrer"`
        linkActiveClass: "foo", // defaults to `nuxt-link-active`
        linkExactActiveClass: "bar", // defaults to `nuxt-link-exact-active`
        linkPrefetchedClass: "baz", // defaults to `nuxt-link-prefetched`
    },
});

Props

Routing

to (string)

⚪ Status: to be discussed

The to prop accepts any kind of URLs.

<NuxtLink to="/foo">...</NuxtLink>
<!-- Renders as: -->
<RouterLink to="/foo">...</RouterLink>

<NuxtLink to="https://example.com">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

Errors & warnings

href (string)

⚪ Status: to be discussed

The href prop is an alias to the to prop.

<NuxtLink href="/foo">...</NuxtLink>
<!-- Renders as: -->
<RouterLink to="/foo">...</RouterLink>

<NuxtLink href="https://example.com">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

Errors & warnings

Attributes

blank (boolean)

⚪ Status: to be discussed

The blank prop is a sugar for making any kind of link open in a new tab.

<NuxtLink to="/foo" blank>...</NuxtLink>
<!-- Renders as: -->
<a href="/foo" target="_blank" rel="noopener noreferrer">...</a>

<NuxtLink to="https://example.com">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">...</a>

target (string)

⚪ Status: to be discussed

The target prop allows users to set the target attribute of the rendered link.

<NuxtLink to="/foo" target="_blank">...</NuxtLink>
<!-- Renders as: -->
<a href="/foo" target="_blank" rel="noopener noreferrer">...</a>

<NuxtLink to="https://example.com" target="_blank">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" target="_blank" rel="noopener noreferrer">...</a>

<NuxtLink to="https://example.com" blank target="bar">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" target="bar" rel="noopener noreferrer">...</a>

Setting target prop to _blank performs effectively the same action as using the blank prop alone.

rel (string)

⚪ Status: to be discussed

The rel prop allows users to set the rel attribute of the rendered link.

<NuxtLink to="/foo" rel="noopener">...</NuxtLink>
<!-- Renders as: -->
<a href="/foo" rel="noopener">...</a>

<NuxtLink to="https://example.com" rel="noopener">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener">...</a>

<NuxtLink to="https://example.com" blank rel="bar">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" target="_blank" rel="bar">...</a>

Prefetching

prefetch (boolean)

⚪ Status: to be discussed

The prefetch prop explicitely enables link prefetching.

<!-- Will prefetch -->
<NuxtLink to="/foo" prefetch>...</NuxtLink>

<!-- No effect -->
<NuxtLink to="https://example.com">...</NuxtLink>

Errors & warnings

no-prefetch (boolean)

⚪ Status: to be discussed

The no-prefetch prop explicitely disables link prefetching.

<!-- Will not prefetch -->
<NuxtLink to="/foo" no-prefetch>...</NuxtLink>

<!-- No effect -->
<NuxtLink to="https://example.com">...</NuxtLink>

Errors & warnings

Styling

active-class (string)

⚪ Status: to be discussed

The active-class prop is simply forwarded to the <RouterLink> component when one is rendered.

<NuxtLink to="/foo" active-class="baz">...</NuxtLink>
<!-- Renders as on `/foo/bar`: -->
<RouterLink to="/foo" class="baz">...</RouterLink>

<NuxtLink to="https://example.com" active-class="baz">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

See Vue Router's <RouterLink> props for more details.

exact-active-class (string)

⚪ Status: to be discussed

The exact-active-class prop is simply forwarded to the <RouterLink> component when one is rendered.

<NuxtLink to="/foo/bar" exact-active-class="baz">...</NuxtLink>
<!-- Renders as on `/foo/bar`: -->
<RouterLink to="/foo" class="nuxt-link-active baz">...</RouterLink>

<NuxtLink to="https://example.com" exact-active-class="baz">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

See Vue Router's <RouterLink> props for more details.

prefetched-class (string)

⚪ Status: to be discussed

The prefetched-class prop is appended to rendered classes once the internal link has been prefetched.

<NuxtLink to="/foo" prefetched-class="bar">...</NuxtLink>
<!-- Renders as, once prefetched: -->
<RouterLink to="/foo" class="bar">...</RouterLink>

<NuxtLink to="https://example.com" prefetched-class="bar">...</NuxtLink>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

Vue Router's <RouterLink> additional props

replace (boolean)

⚪ Status: to be discussed

The replace prop is simply forwarded to the <RouterLink> component when one is rendered.

See Vue Router's <RouterLink> props for more details.

aria-current-value (string)

⚪ Status: to be discussed

The aria-current-value prop is simply forwarded to the <RouterLink> component when one is rendered.

See Vue Router's <RouterLink> props for more details.

Edge cases handling

external (boolean)

⚪ Status: to be discussed

The external prop forces the link to be rendered as an external link.

<NuxtLink to="/foo" external>...</NuxtLink>
<!-- Renders as: -->
<a href="/foo" rel="noopener noreferrer">...</a>

<a to="https://example.com" external>...</a>
<!-- Renders as: -->
<a href="https://example.com" rel="noopener noreferrer">...</a>

Errors & warnings

internal prop (boolean)

⚪ Status: to be discussed

The internal prop forces the link to be rendered as an internal link.

<NuxtLink to="/foo" internal>...</NuxtLink>
<!-- Renders as: -->
<NuxtLink to="/foo">...</NuxtLink>

<a to="https://example.com" internal>...</a>
<!-- Renders as: -->
<NuxtLink to="https://example.com">...</NuxtLink>

Errors & warnings