next-page-map
v1.0.1
Published
Get a mapping of pathnames to filenames from a Next.js pages directory
Downloads
230
Readme
next-page-map
Get a mapping of files to page paths from your Next.js pages directory.
Installation
npm i next-page-map
Usage
This can be used to build navigation trees and construct links to edit the current page on GitHub. It walks the pages
directory synchronously, so it's best placed in your next.config.js
and the result passed via publicRuntimeConfig
:
const getPageMap = require('next-page-map')
const pageMap = getPageMap()
module.exports = {
publicRuntimeConfig: {pageMap},
// the rest of your config
}
Options
The getPageMap()
default export takes an optional object of options:
| Key | Description | Default |
| :-- | :--- | :--- |
| dir
| the directory from which to resolve pages
| the current working directory (process.cwd()
) |
| pageExtensions
| an optional array of filename extensions to match | ['js', 'jsx']
|
| nested
| a boolean to enable the nested object format | false
|
Map format
The default return format is an object mapping URIs (keys) to filenames (values). Both values have a leading slash, e.g.
/* given the following file structure:
pages
├── about
│ ├── index.js
│ ├── mission.js
│ └── team.js
├── index.js
└── news.js
*/
console.log(JSON.stringify(getPageMap(), null, 2))
/* outputs:
{
"/": "/index.js",
"/about": "/about/index.js",
"/about/mission": "/about/mission.js",
"/about/team": "/about/team.js",
"/news": "/news.js"
}
*/
Nested format
If you'd like to build nested navigation from the page map, you can pass nested: true
in the options object, which causes getPageMap()
to return a recursive structure with the form:
{
file: '/path/to/foo/index.js', // the file name relative to <cwd>/pages
path: '/path/to/foo', // the URI (path minus page extension and trailing "/index")
isIndex: true, // whether the file w/o extention ends in "/index"
parent: '/path/to', // the path of the "parent" page
children: [<pages>] // an array of objects whose `.parent` === this.path
}
Examples
Edit links
The map format can be used to look up the filename from any component that uses Next's router, including App components:
// pages/_app.js
import App, {Container} from 'next/app'
import getConfig from 'next/config'
const {pageMap} = getConfig().publicRuntimeConfig
// TODO: replace "<owner>" and "<repo>" with your repo's slugs,
// and replace "master" if that's not your default branch
const editBaseURL = 'https://github.com/<owner>/<repo>/edit/master/pages'
const editURL = filename => `${editBaseURL}${filename}`
export default class extends App {
render() {
const {pathname} = this.props.router
const filename = pageMap[pathname]
return (
<Container>
{/* render your stuff */}
{filename && (
<p>
<a href={editURL(filename)}>Edit this page on GitHub</a>
</p>
)}
</Container>
)
}
}
Nested navigation
This example uses nested format with webpack's require.context to get a handle on the actual components that each page renders, then tries to get the text for the link from its displayName
:
// next.config.js
const getPageMap = require('next-page-map')
const pageExtensions = ['js', 'mdx']
module.exports = {
pageExtensions,
publicRuntimeConfig: {
pageExtensions,
pageMap: getPageMap({pageExtensions, nested: true})
}
// ...
}
// src/Nav.js
import getConfig from 'next/config'
import NextLink from 'next/link'
import {withRouter} from 'next/router'
const {pageExtensions, pageMap} = getConfig().publicRuntimeConfig
const pattern = new RegExp(`\.(${pageExtensions.join('|')}$`)
const requirePage = require.context('../pages', true, pattern)
export default function Nav(props) {
return (
<nav {...props}>
<NavList links={pageMap} />
</nav>
)
}
const NavList = ({links, ...rest}) => (
<ul {...rest}>
{links.map(link => (
<li>
<NavLink link={page} />
</li>
))}
</ul>
)
// Note: the withRouter() HOC gives this component a `router` prop,
// which memorializes the `pathname` of the current page
const NavLink = withRouter(({link, router, ...rest}) => {
const {file, path, children} = link
const current = router.pathname === path
// require.context().keys() returns all of the matched filenames,
// but they're relative to the '../pages' path, so the only real
// difference is the leading "."
const contextPath = requirePage.keys().find(key => key === `.${file}`)
let text = file
if (contextPath) {
const Page = requirePage(contextPath)
text = Page.displayName || Page.name || text
}
return (
<NextLink to={path}>
<a href={path} aria-current={current} {...rest}>{text}</a>
</NextLink>
)
})