@hashicorp/nextjs-scripts
Next.js plugin for configuring Next for HashiCorp-style websites.
Last updated 5 days ago by jescalan .
MPL-2.0 · Original npm · Tarball · package.json
$ cnpm install @hashicorp/nextjs-scripts 
SYNC missed versions from official npm registry.

HashiCorp Nextjs Scripts

This tool layers a number of configuration choices, code quality checks, and code generators on top of next.js. Specifically, it provides:

  • Baked in, zero-config typescript linting & prettier formatting via binary
  • Code generators for base website templates, new pages, and new components via binary
  • A pre-configured client for easily fetching from DatoCMS
  • A strong set of default plugins, including:
    • mdx-processed markdown with front-matter and layouts
    • css files with pre-configured postcss-preset-env can be imported directly into components
    • graphql file loader
    • webpack bundle analyzer

Table Of Contents

Quick reference on how to create a new website template: npx @hashicorp/nextjs-scripts generate website

Basic Usage & Options

The plugin looks like this inside of your next.config.js file:

const withHashicorp = require('@hashicorp/nextjs-scripts')

module.exports = withHashicorp(/* options */)(/* normal nextjs config */)

Let's go through the full options:

withHashicorp({
  // passed directly to next-mdx-enhanced
  // see options here: https://github.com/hashicorp/next-plugin-mdx-enhanced#readme
  mdx: {
    layoutsPath: 'somePath/otherPath',
    defaultLayout: true,
    // passes the value to `resolveFrom` include-markdown plugin
    // https://github.com/hashicorp/remark-plugins/blob/master/plugins/include-markdown/README.md#options
    resolveIncludes: __dirname,
    pluginOptions: {
      // https://github.com/hashicorp/remark-plugins
      anchorLinks: {
        compatibilitySlug: (text) => text,
        listWithInlineCodePrefix: 'inlinecode',
      },
    },
  },
  css: {
    plugins: [somePlugin(), otherPlugin()], // array of postcss plugins
    presetEnvOptions: { stage: 3 }, // https://github.com/csstools/postcss-preset-env#options
  },
  dato: { token: 'xxx' }, // if necessary, override the default datocms token with your own
  transpileModules: ['foo'], // third party package names that should be transpiled by babel
})

All of these are optional, none are required to make withHashicorp function properly. In fact, we recommend not using any custom options unless you need to.

Default Plugins and Enhancements

Out of the box, this plugin adds a couple useful utilities:

These can both be used in any project implementing nextjs-scripts as described in their readmes.

The Binary

nextjs-scripts ships with a binary (next-hashicorp) that includes a variety of useful tools, which we will go through below. Generally, we recommend using npx or a local install and npm scripts to run the binary, rather than installing globally.

Linting & Formatting

nextjs-scripts provides centrally managed, pre-configured ESLint and Prettier tasks which can be executed via next-hashicorp lint and next-hashicorp format respectively. We recommend installing locally and running them as npm tasks. We prefer to run both of these tasks before any commit can be made -- if you share that preference, you can execute both using the command next-hashicorp precommit.

If you would like to change the configuration or use a different configuration for any of these tasks, we'd recommend forking the project and changing it to match your preferences. The purpose of a controlled, centralized config is to ensure that all projects that implement it are consistent, and allowing per-project config changes eliminates this benefit.

Stylelint Configuration

Nextjs-scripts configures the following Stylelint plugins, listed below:

  • stylelint-config-standard with stylelint-config-prettier to skip prettier-managed rules.
  • stylelint-media-use-custom-media to enforce usage of known custom media queries.
  • stylelint-value-no-unknown-custom-properties to enforce usage of known custom properties.
  • stylelint-order to alphabetize style declarations.
  • stylelint-use-nesting to enforce proper CSS nesting.
  • stylelint-use-nesting to enforce proper CSS nesting.

You can modify the Stylelint configuration in your local stylelintrc.js file.

// .stylelintrc.js with configured rules of custom media and custom properties.
module.exports = {
  ...require('@hashicorp/nextjs-scripts/.stylelintrc.js'),
  rules: {
    'csstools/media-use-custom-media': [
      'known',
      {
        importFrom: [
          './node_modules/@hashicorp/react-global-styles/custom-media.css',
        ],
      },
    ],
    'csstools/value-no-unknown-custom-properties': [
      true,
      {
        importFrom: [
          './node_modules/@hashicorp/react-global-styles/custom-properties/color.css',
          './node_modules/@hashicorp/react-global-styles/custom-properties/font.css',
        ],
      },
    ],
  },
}

Generators

nextjs-scripts also provides a few generators that can provision templates for common assets. At the moment, this includes:

  • next-hashicorp generate website - creates a new, bare-bones website template that idiomatically implements next-hashicorp tooling
  • next-hashicorp generate component - creates a new component template in your components folder
  • next-hashicorp generate page - creates a new page template in your pages folder

After running these commands, you will be asked a couple questions, then your files will be generated.

GraphiQL

We provide a handy bin command that opens up Dato's in-browser GraphiQL IDE in your default browser. The URL to this IDE can be a bit annoying to track down because you need to have your API Token handy but since nextjs-scripts hangs on to this, we can avoid that step.

next-hashicorp graphiql

If you're unfamiliar with what GraphiQL provides you, please have a look at the GraphiQL repo.

Markdown Compilation

nextjs-scripts comes pre-configured to compile markdown files using mdx. Any .mdx file included in the pages folder will automatically output as a page. Additionally we ship a custom loader that allows the parsing of yaml front matter, and the ability to render markdown files into layouts. This layout rendering process is a little involved and we'll go into it further below.

The markdown file itself is the easy part - simply create a .mdx file in the pages directory and you're set. If you use frontmatter, it will be parsed and available within the layout and any loaders used to process the file, and is parsed using gray-matter.

To implement a layout, first create a layouts directory within the pages directory and add a file. Then using the layout property of your front matter, enter the name of the layout file, relative to pages/layouts. Finally, we need to populate the layout file properly using mdx's layout system. Here's a minimal example of how this whole setup might look. First, the markdown file:

---
title: 'Testing Page'
layout: 'test-layout'
---

Hello **world**!

And then your layout, at pages/layouts/test-layout.jsx:

import { MDXProvider } from '@mdx-js/tag'

export default (frontmatter) => {
  return ({ children }) => (
    <MDXProvider>
      <h1>{frontmatter.page_title}</h1>
      {children}
    </MDXProvider>
  )
}

A couple things going on here. First we import the mdx provider, which we wrap our layout template with in order to properly render the markdown/react-component contents. Then we simply export a function, which is passed the front matter, and return whatever layout we'd like as long as it's wrapped with MDXProvider. Wherever you want to render the markdown content of each file that implements the layout, use children in the same way as you might with a higher order component. Nice and easy!

NOTE: Our custom loader adds one special variable, __resourcePath, to the markdown for each file. You can use this variable within your layout to determine the original path of the file that is being shown, relative to /pages.

We also add syntax highlighting using prism to all code blocks. In order to use the accompanying styles, you can import @hashicorp/nextjs-scripts/prism/style.css into your main stylesheet!

Loading From DatoCMS

We use DatoCMS as an interface through which our non-technical staff can have the ability to modify content on our websites. Dato is not used on every part of every page, rather as we are building each site we decide which areas to add it to and what to make editable.

There are two different strategies for data loading, and depending on the scenario, you should use different tools and techniques to get it done.

DatoCMS exposes two endpoints. One provides production ready, published content. The other also returns records that are in a saved, but unpublished state for previewing. Setting HASHI_ENV=preview in your environment will use the preview endpoint and return unpublished records. The default is to return production only records to avoid unexpectedly exposing preview content.

Loading Initial Data

If you need to load a set of initial data in order to render a component, and that data does not change at all after the initial load, you should use getStaticProps to do it. nextjs-scripts provides a pre-configured graphql request client that can be used to fetch data from DatoCMS as such:

import fetch from '@hashicorp/nextjs-scripts/dato/client'
import query from './query.graphql'

function someComponent({ posts }) {
  return <p>{JSON.stringify(posts)}</p>
}

export async function unstable_getStaticProps() {
  const { posts } = await fetch({ query })
  return { posts }
}

This will integrate nicely with nextjs, ensuring that the necessary data is loaded before the page renders for client-side routing, and fetching on the server or at build time for dynamic and static build outputs, respectively.

Loading Dynamic Data

If you have more complex data fetching needs such as:

  • you want to render the page first then fetch data after for only one portion of the page
  • you want to fetch data in response to user input or client-side timers
  • you want to re-fetch the initial data in response to user input or timers
  • you want to make several different data fetching requests in parallel and render their outputs on the page as soon as they are available

You will need a more powerful tool than a blocking function that loads data only for the initial render. If you have run into this scenario, let's talk about it as a team -- we don't have a solution prepared as we haven't yet encountered this situation, but we have spent some time tinkering with tools like Apollo and URQL in the past which can be potential solutions.

CSS Processing

Nextjs-scripts configures a standard stack of postcss plugins, listed below:

If you'd like to add extra plugins before or after the stack, or change the options passed to postcss-preset-env, you can control this via the css options as such:

withHashicorp({
  css: {
    beforePlugins: [plugin1, plugin2],
    afterPlugins: [plugin3],
    presetEnvOptions: { nesting: false },
  },
})(/* normal nextjs config */)

Utilities

There are a few utility scripts for commonly used conventions in HashiCorp sites which are detailed below.

Bugsnag Configuration

It's nice and easy to set up Bugsnag with the central config in nextjs-scripts. To pull down and initialize the client, you can import it as such:

import Bugsnag from '@hashicorp/nextjs-scripts/lib/bugsnag'

Just make sure that you have defined BUGSNAG_CLIENT_KEY and BUGSNAG_SERVER_KEY as environment variables. It requires two keys because nextjs can render javascript on the client and server, and will interact with the service differently depending on the environment. The first time this import runs, the client will be initialized.

If you want to just pull down the ErrorBoundary component, this can be imported directly as such:

import { ErrorBoundary } from '@hashicorp/nextjs-scripts/bugsnag'

NProgress

By default, nextjs does not provide any loading indicator for client-side route transitions. They recommend the use of NProgress, a small script that dislays a loading bar at the top of the browser frame.

It can be added to your app as such, within _app.js

import '@hashicorp/nextjs-scripts/lib/nprogress/style.css'
import NProgress from '@hashicorp/nextjs-scripts/lib/nprogress'
import Router from 'next/router'

NProgress({ Router })

If you want to add some custom action to the route change's start, finish, or error states, you can pass in functions that will run accordingly:

import '@hashicorp/nextjs-scripts/lib/nprogress/style.css'
import NProgress from '@hashicorp/nextjs-scripts/lib/nprogress'
import Router from 'next/router'

NProgress({
  Router,
  start: () => console.log('route change started'),
  finish: () => console.log('route change complete'),
  error: () => console.log('route change error'),
})

It's worth noting that the finish handler will always automatically fire an analytics page event as long as the window.analytics object is present.

Make sure to remember the css import as well!

Consent Manager

It is required that we use a consent manager for any and all scripts that track personal data, analytics included. We have a custom script that provides this functionality that can be brought in via nextjs-scripts as such:

import createConsentManager from '@hashicorp/nextjs-scripts/lib/consent-manager'

const { ConsentManager, openConsentManager } = createConsentManager({
  segmentWriteKey: 'xxx', // production only - staging/local key populated automatically in dev
  preset: ''              // optional but strong recommended: pick one: 'enterprise' or 'oss'
  segmentServices: [],    // optional: maps to `segmentServices` key
  otherServices: [],      // optional: maps to `additionalServices` key
  categories: [],         // optional: maps to `categories` key
})

For more detail on the segmentServices, otherServices, and categories keys, see the consent manager documentation

The return values are the consent manager component, fully configured, which can be initialized empty like <ConsentManager />, and an openConsentManager function - whenever this is called it will open up the consent manager interface. We typically have a link in the footer that opens up these preferences.

By default, there will be no services loaded into the consent manager, it is strongly recommended to use one of the presets, oss or enterprise. The services included in each are detailed below:

OSS

  • Google Analytics
  • Optinmonster

Enterprise

  • Google Analytics
  • Google Tag Manager
  • Marketo
  • Heap
  • LinkedIn Insights

Any other services added via the segmentServices or otherServices configuration keys will be added to and not overwrite services loaded in by presets. As a note, if you want to add one of the services that exists in either of the above lists, but is not included in your preset, or if you are building your own set of services, you can import services pre-configured as in the example below:

import createConsentManager from '@hashicorp/nextjs-scripts/lib/consent-manager'
import services from '@hashicorp/nextjs-scripts/lib/consent-manager/services'

const { ConsentManager, openConsentManager } = createConsentManager({
  segmentWriteKey: 'xxx',
  preset: 'oss',
  segmentServices: [services.marketo],
})

As a general note, if you find yourself adding additional services or deviating significantly from the presets, it's a good idea to consult with the team first. The more services we add, the more we bloat the size and load time of our websites, so we try to be as minimal as possible while also serving the needs of the marketing organization.

Anchor Link Analytics

HashiCorp maintains a lot of documentation sites, all of which have many automatically generated permalinks based on headline text, and which can often break as a result of text changes and reorganization. As such, we try to run some extra analytics on permalinks by tracking, when a page url contains an anchor link (like hashicorp.com#foo) whether the given anchor is actually present on the page. This allows us to more confidently remove custom anchor links that are unused, and to detect when a popular incoming anchor link is broken so it can be fixed.

To enable this tracking, simply import @hashicorp/nextjs-scripts/lib/anchor-link-analytics in your _app.js. This script is SSR-compatible and runs inside requestIdleCallback so that it has a minimal impact on page performance. An example of a bare bones implementation:

import useAnchorLinkAnalytics from '@hashicorp/nextjs-scripts/lib/anchor-link-analytics'

export default function App({ Component, pageProps }) {
  useAnchorLinkAnalytics()
  return <Component {...pageProps} />
}

Publishing

To publish this package to npm, run one of the following command depending on how you want to boost your version:

  • npm run release:major
  • npm run release:minor
  • npm run release:patch

This command will walk you through the release process and then publish to npm.

Note: There is no build step when publishing this library. The consumer is expected to transpile the code appropriately.

Current Tags

  • 8.2.0                                ...           latest (5 days ago)
  • 6.1.0-prism.1                                ...           prism (22 days ago)

68 Versions

  • 8.2.0                                ...           5 days ago
  • 8.1.0                                ...           6 days ago
  • 8.0.0                                ...           7 days ago
  • 7.2.1                                ...           7 days ago
  • 7.2.0                                ...           11 days ago
  • 7.1.2                                ...           13 days ago
  • 7.1.1                                ...           13 days ago
  • 7.1.0                                ...           14 days ago
  • 7.0.0                                ...           20 days ago
  • 6.2.0                                ...           21 days ago
  • 6.1.0-prism.1                                ...           22 days ago
  • 6.1.0-prism.0                                ...           a month ago
  • 6.1.0                                ...           a month ago
  • 6.0.0                                ...           a month ago
  • 6.2.0-12                                ...           a month ago
  • 6.2.0-11                                ...           a month ago
  • 6.2.0-10                                ...           2 months ago
  • 6.0.0-5                                ...           2 months ago
  • 6.2.0-9                                ...           2 months ago
  • 6.2.0-8                                ...           2 months ago
  • 6.0.0-4                                ...           2 months ago
  • 6.0.0-3                                ...           2 months ago
  • 6.2.0-7                                ...           2 months ago
  • 6.2.0-6                                ...           2 months ago
  • 6.2.0-5                                ...           2 months ago
  • 6.2.0-4                                ...           3 months ago
  • 6.2.0-3                                ...           3 months ago
  • 6.2.0-2                                ...           3 months ago
  • 6.2.0-1                                ...           3 months ago
  • 6.2.0-0                                ...           3 months ago
  • 5.4.1                                ...           3 months ago
  • 5.4.0                                ...           3 months ago
  • 6.1.0-1                                ...           3 months ago
  • 6.1.0-0                                ...           3 months ago
  • 5.3.2-0                                ...           3 months ago
  • 6.0.0-2                                ...           4 months ago
  • 5.3.1                                ...           4 months ago
  • 5.3.0                                ...           5 months ago
  • 5.3.0-0                                ...           5 months ago
  • 6.0.0-1                                ...           5 months ago
  • 6.0.0-0                                ...           6 months ago
  • 5.2.6                                ...           6 months ago
  • 5.2.5                                ...           6 months ago
  • 5.2.4                                ...           6 months ago
  • 5.2.3                                ...           6 months ago
  • 5.2.2                                ...           6 months ago
  • 5.2.1                                ...           6 months ago
  • 5.2.0                                ...           6 months ago
  • 5.1.0                                ...           7 months ago
  • 5.0.0                                ...           7 months ago
  • 4.7.0                                ...           7 months ago
  • 4.6.0                                ...           7 months ago
  • 4.5.2                                ...           8 months ago
  • 4.5.1                                ...           8 months ago
  • 4.5.0                                ...           8 months ago
  • 4.4.0                                ...           8 months ago
  • 4.3.1                                ...           8 months ago
  • 4.3.0                                ...           8 months ago
  • 4.2.0                                ...           8 months ago
  • 4.1.0                                ...           8 months ago
  • 4.0.0                                ...           9 months ago
  • 4.0.0-1                                ...           9 months ago
  • 4.0.0-0                                ...           9 months ago
  • 3.0.3                                ...           9 months ago
  • 3.0.2                                ...           9 months ago
  • 3.0.2-0                                ...           9 months ago
  • 3.0.1                                ...           9 months ago
  • 3.0.0                                ...           9 months ago

Copyright 2014 - 2016 © taobao.org |