question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Goals

  • Have a dependency graph of pages that depend on each other at build time for smart bundling optimizations
  • Smaller runtime for linking to other pages
  • Getting rid of cloneElement
  • Easier to understand API, no more href vs as
  • Being able to lint for links that we know at build time will always 404
  • Replace next/link but retaining backwards compatibility with it.
  • Easier to understand dynamic routing, as there’s a distinction between the page being rendered and the url.
  • Get rid of buildId in the page file url to improve long-term caching.

API

Simple usage:

// pages/index.js
import { useLink } from 'next/link'

function HomePage() {
  const AboutLink = useLink('/about', () => import('./about'))
  return <>
    Go to about page
    <AboutLink>About</AboutLink>
  </>
}

So what does useLink return?

  • An enhanced <a> component with href , onClick and ref set

  • Automatically prefetches based on viewport

  • import() allows linters to throw when the file doesn’t exist (TS, eslint etc)

  • You no longer have to write <a> inside of <Link>

Alternatives

An alternative API that involves code-generation and is harder to type using TypeScript would be:

// pages/index.js
import { A as AboutLink } from './about'

function HomePage() {
  return <>
    Go to about page
    <AboutLink>About</AboutLink>
  </>
}

It would give the same linting / 404 detection benefits as the import API, we’d abstract it away from the user using code generation.


One issue that was raised by @lydiahallie is that creating a navbar would look like this:

const HomeLink = useLink('/', () => import('./index'))
const BlogLink = useLink('/blog', () => import('./blog'))
const BlogHelloWorldLink = useLink('/blog/hello-world', () => import('./blog/hello-world'))
const SupportLink = useLink('/support', () => import('./support'))
const SomethingElseLink = useLink('/something-else', () => import('./something-else'))
const AboutLink = useLink('/about', () => import('./about')) // Copied as filler to give same effect
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))
const AboutLink = useLink('/about', () => import('./about'))

The solution for doing something like that might be using returned hook value:

const links = [
  useLink('/', () => import('./index')),
  useLink('/blog', () => import('./blog')),
  useLink('/blog/hello-world', () => import('./blog/hello-world')),
  useLink('/support', () => import('./support')),
  useLink('/something-else', () => import('./something-else')),
  // etc
]

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:23
  • Comments:20 (15 by maintainers)

github_iconTop GitHub Comments

4reactions
anthonyshortcommented, May 15, 2019

I love the idea of using a hook API for links. One of the most awkward APIs in Next is the way the links work. They are:

  • Hard to style. If you’re using something like styled components or wanting to link using a button, you end up needing to drop down to using the router directly.
  • Magic: The way that it clones the element and injects the href is confusing. Especially for new people. Having links in the tree without a href can make linters complain.

I’d suggest that the goal of the link API should be:

  • Explicit. Returning handlers enables the end-user to then create their own link component that might be styled using whatever CSS system they’ve already got setup.
  • Consistent: We should be able to use the same API when we’re trying to do any routing/linking with an app.

For example, in use-next-route I used this API:

const { href, onClick, navigate } = useLink('/projects')

This lets me decouple the styled links from next functionality. My projects will usually have a set of low-level UI components. Let’s say I’m using styled-components:

const { href, onClick, navigate } = useLink('/projects')

return (
  <StyledLink href={href} onClick={onClick} />
)

or if I want a button click to trigger a navigation, the API stays the same:

const { href, onClick, navigate } = useLink('/projects')

return (
  <button onClick={onClick}>Go there</button>
)

or if I want to navigate on a form submission:

const { href, onClick, navigate } = useLink('/projects')

function onSubmit() {
  // handle the form
  navigate()
} 

return (
  <form onSubmit={navigate}>...</form>
)

There’s also the problem wanting to pass in parameters. You can do this when you create the link, just like in Router.push:

const projectRoute = useRoute({
    pathname: '/project/details',
    query: {
      id: props.project.id
    }
})

or you can pass in params when you call navigate, for times when you don’t know what the parameters are during render:

const { navigate } = useLink()

function onSubmit() {
  navigate({
    pathname: '/project/details',
    query: {
      id: props.project.id
    }
  })
}

One benefit of doing it this way is you can pull out all of your routes into another file, so you can re-use the logic/parameters, regardless of what type of element is trying to trigger the navigation. In my apps, I have a hooks/routes.ts file that contains all of the routes that any component can use:

export function useProjectRoute(projectId: string) {
  return useRoute({
    pathname: '/project/details',
    query: {
      id: projectId
    }
  })
}

If we were to return an anchor element it limits the ways the route could be used. Let’s say we tried to follow a similar pattern:

export function useProjectRoute(projectId: string) {
  return useLink('/projects/details', () => import('./about')) 
}

This returns an <a>, but I still want to pass in the projectId and I might want to trigger this route after a form submission or on a button click.

I’m not too sure how this might affect the ability to create a dependency graph, but maybe we could have a combination of the two:

  return { href, onClick, navigate } = useLink('/project/details', () => import('/project/details'))

with the option to pass in parameters:

return { href, onClick, navigate } = useLink({
  pathname: '/project/details'
}, () => import('/project/details'))

But it looks a little awkward. I’m not too sure about the mechanics of the import so I think if I understood that a little more I could suggest something better 😃

2reactions
jth0024commented, May 20, 2019

How would you manage rendering links to unknown routes with this API? For example, in our app we receive a list of menu links from an external CMS. Since our links are dynamic, this proposal wouldn’t work for us. We wouldn’t know which file to import and we would have to use string interpolation in the import statements. From what I understand, interpolated import statements cause a lot more code to be shipped to the client than expected (Webpack has to bundle every possible match). Here’s an example to illustrate the issue I’m describing:

// Topbar.js
function Topbar(props) {
  const { links } = this.props
  return (
    <header>
      {...links.map(link => (<TopbarLink {...link}/>))}
    <header/>
  )
}

// TopbarLink.js
import { useLink } from 'next/link'

function TopbarLink(props) {
  const { page, text } = props
  const Link = useLink(page, () => import(`./${page}`))
  return <>
    <Link>{text}</Link>
  </>
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

RFC 2757: Long Thin Networks
A better approach is to use link-layer mechanisms such as FEC, retransmissions, and so on in order to improve the characteristics of the...
Read more >
Comments on NWG/RFC 33 and 36 - IETF
This was referred to in RFC #33 as "switch". ... Thus, except for the RFC command, all commands can use link numbers and...
Read more >
RFC du Jour (@rfcdujour) / Twitter
#RFC9339 [NEW] This document specifies the extensions to OSPF that enable a router to use Link-Local Signaling (LLS) to signal the metric that...
Read more >
Link-local address - Wikipedia
S. Cheshire; B. Aboba; E. Guttma (May 2005). Dynamic Configuration of IPv4 Link-Local Addresses. The Internet Society. doi:10.17487/RFC3927. RFC 3927.
Read more >
RFC 9339: - The RFC Archive
Permanent link to RFC 9339 ... Show other RFCs mentioning RFC 9339 ... to OSPF that enable a router to use Link-Local Signaling...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found