DEV Community

Solufa
Solufa

Posted on • Updated on

How to get Next.js internal links in a type-safe way

Problem

There is Link that transitions to /post/1.

import Link from 'next/link'

export default () => {
  const url = `/post/${1}`
  return <Link href={url} />
}
Enter fullscreen mode Exit fullscreen mode

Cannot statically type-check if /post/${pid} is correct as an internal link.
If the file pages/post/[pid].tsx does not exist, the page transition will fail.

Solution

Pathpida solves this problem.
Reads pages/ directory in Next.js and automatically generates a type definition file for getting internal links.

pathpida logo

For example, if pages/ directory is in the following state

pages/[pid].tsx
pages/blog/[...slug].tsx
pages/index.tsx
Enter fullscreen mode Exit fullscreen mode

the following TypeScript file will be generated automatically.

lib/$path.ts

export const pagesPath = {
  _pid: (pid: number | string) => ({
    $url: () => ({ pathname: '/[pid]', query: { pid }})
  }),
  blog: {
    _slug: (slug: string[]) => ({
      $url: () => ({ pathname: '/blog/[...slug]', query: { slug }})
    })
  },
  $url: () => ({ pathname: '/' })
}
Enter fullscreen mode Exit fullscreen mode

As you can see, pagesPath property corresponds to a file in pages/ directory.
The object returned by $url method can be passed directly to next/link and next/router.

In the component, you can use it as follows.

import Link from 'next/link'
import { pagesPath } from '../lib/$path'

export default () => {
  return <Link href={pagesPath.blog._slug(['a', 'b', 'c']).$url()} />
}
Enter fullscreen mode Exit fullscreen mode

Usage in Next.js

This explanation assumes that Next.js and TypeScript have been set up beforehand.

Install pathpida and npm-run-all.

$ yarn add pathpida npm-run-all --dev
Enter fullscreen mode Exit fullscreen mode

package.json

{
  "scripts": {
    "dev": "run-p dev:*",
    "dev:next": "next dev",
    "dev:path": "pathpida --watch",
    "build": "pathpida && next build"
  }
}
Enter fullscreen mode Exit fullscreen mode

Start Next.js and pathpida in monitoring mode with Dev command.

$ yarn dev
Enter fullscreen mode Exit fullscreen mode

$path.ts file will be automatically created in either utils/ or lib/ directory.

lib/
  $path.ts
pages/
  _app.tsx
  index.tsx
Enter fullscreen mode Exit fullscreen mode

lib/$path.ts

export const pagesPath = {
  $url: () => ({ pathname: '/' })
}
Enter fullscreen mode Exit fullscreen mode

If you add more files to pages/ directory, lib/$path.ts file will be changed.

pagesPath can be imported and used with next/link or next/router.

lib/
  $path.ts
pages/
  articles/
    [id].tsx
  users/
    [...userInfo].tsx
  _app.tsx
  index.tsx
Enter fullscreen mode Exit fullscreen mode

components/ActiveLInk.tsx

import Link from 'next/link'
import { useRouter } from 'next/router'
import { pagesPath } from '../lib/$path'

function ActiveLink() {
  const router = useRouter()

  const handleClick = () => {
    router.push(pagesPath.users._userInfo(['mario', 'hello', 'world!']).$url())
  }

  return <>
    <div onClick={handleClick}>Hello</div>
    <Link href={pagesPath.articles._id(1).$url()}>
      World!
    </Link>
  </>
}

export default ActiveLink
Enter fullscreen mode Exit fullscreen mode

How to specify a query

You can specify a query just by exporting Query type in pages/ component.
For example, to create /user?userId={number}, write the following

pages/user.tsx

export type Query = {
  userId: number
}

export default () => <div />
Enter fullscreen mode Exit fullscreen mode

To set a query in another component, write the following

import { pagesPath } from '../lib/$path'

pagesPath.user.$url({ query: { userId: 1 }})
Enter fullscreen mode Exit fullscreen mode

How to specify a hash

Just pass hash as an argument of $url method with any string you like.
# will be given automatically.

import { pagesPath } from '../lib/$path'

pagesPath.user.$url({ query: { userId: 1 }, hash: 'hoge' })
pagesPath.user.$url({ hash: 'fuga' })
Enter fullscreen mode Exit fullscreen mode

You can also get the file path of public/ directory

If you pass --enableStatic option to pathpida command, it will read the contents of public/ directory and generate a staticPath client.

package.json

{
  "scripts": {
    "dev": "run-p dev:*",
    "dev:next": "next dev",
    "dev:path": "pathpida --enableStatic --watch",
    "build": "pathpida --enableStatic && next build"
  }
}
Enter fullscreen mode Exit fullscreen mode

If you have JSON and PNG images in public/

public/aa.json
public/bb/cc.png

lib/$path.ts or utils/$path.ts
Enter fullscreen mode Exit fullscreen mode

staticPath client can get the path string directly instead of $url method.

pages/index.tsx

import Link from 'next/link'
import { staticPath } from '../lib/$path'

console.log(staticPath.aa_json) // /aa.json

export default () => <img src={staticPath.bb.cc_png} />
Enter fullscreen mode Exit fullscreen mode

Thanks for reading to the end

I'd appreciate it if you could give me a star on GitHub!

GitHub logo aspida / pathpida

TypeScript friendly internal link client for Next.js and Nuxt.js.

pathpida


pathpida

TypeScript friendly internal link client for Next.js and Nuxt.js.

Features

  • Type safety. Automatically generate type definition files for manipulating internal links in Next.js/Nuxt.js.
  • Zero configuration. No configuration required can be used immediately after installation.
  • Zero runtime. Lightweight because runtime code is not included in the bundle.
  • Support for static files. Static files in public/ are also supported, so static assets can be safely referenced.

Table of Contents

Install

  • Using npm:

    $ npm install pathpida npm-run-all --save-dev
    Enter fullscreen mode Exit fullscreen mode
  • Using Yarn:

    $ yarn add pathpida npm-run-all --dev
    Enter fullscreen mode Exit fullscreen mode

Setup - Next.js

package.json

{
  "scripts": {
    "dev": "run-p dev:*"
    "dev:next": "next
Enter fullscreen mode Exit fullscreen mode

Top comments (0)