12 minute read

Getting a Next.js application up and running is not a trivial exercise, especially if you want a robust and extensible result that will support a modern development process.

Here’s a plug-and-play Next.js template that offers the following features:

  • Tree-shakable support for the latest ES6 goodies with eslint uber alles.

  • User registration & authentication via NextAuth.js, by default against an AWS Cognito User Pool supporting native username/password authentication and one federated identity provider (Google).

  • Support for public & private API endpoints, both local to NextJS and at any AWS API Gateway secured by the same Cognito User Pool.

  • Configured to act as a front end & authentication client for my AWS API Template on the back end.

  • Fully integrated application state management with the Redux Toolkit, including support for difficult-to-serialize types like Date & BigInt.

  • Responsive UX with Semantic UI React with LESS theme overrides enabled & ready for input!

  • A responsive & attractive sample UI that encapsulates a ton of common use cases into an opinionated architecture and a library of utility components.

  • Automated lodash cherry-picking with babel-plugin-lodash.

  • Front & back-end testing with mocha, chai, and the React Testing Library. Includes examples and a sweet testing console!

  • Code formatting at every save & paste with prettier.

  • One-button release to GitHub with release-it.

See it on GitHub!   Clone the Repo!

This documentation is incomplete! You’ll see TODOs below where I’m still working on it, but since the underlying template is very solid I wanted to get it out there sooner rather than later. I’ll get it all done soon. Meanwhile don’t hesitate to raise an issue for any questions!

Why?

Deploying a vanilla Next.js application is easy, but getting it to a point where it can support real-world requirements is a challenge. This template solves a lot of initial problems and gets you to a well-scaffolded, responsive web application with support for all the goodies, built-in navigation, and a powerful toolbox to drive future development.

This template is highly opinionated with respect to toolchain. It is hard to get all of these bits to work together. It is way easier to cut out the bits you don’t need than figure out how to slot in the things you do. So that’s what we have here.

Because this is a Next.js template, it works perfectly when deployed to Vercel. I’ve tried hard to make it host-agnostic, though, and I know for a fact that it works just as well deployed to AWS Amplify. It should work fine (possibly with some tweaks to the build process) at any host that supports Next.js.

Setting Up Your Dev Environment

Use VS Code as your code editor! Not an absolute requirement, but you’ll be glad you did.

  1. Click here to generate a new repository from this template.

  2. Clone the new repository to your local machine.

  3. VS Code will ask to install a bunch of recommended extensions. Accept all of them. If you miss this prompt, follow these steps:

    • Open the VS Code Extensions tab
    • Enter @recommended into the search box
    • Click the Download link.

  4. Zero the package version and install dependencies by running these commands:

    npm version 0.0.0
    npm install
    
  5. Run your tests from the command line:

    npm run test
    
    # STATE
    #   ENTITY
    #     * validations
    #       ✔ * initializes state
    #     add entities
    #       * validations
    #         ✔ * select all entities
    #         ✔ * select one entity
    #         ✔ * select invalid entity
    #       update entities
    #         validations
    #           ✔ * select all entities
    #       remove entity
    #         validations
    #           ✔ * select all entities
    #
    # back-end test
    #   ✔ passes
    #
    # 7 passing (49ms)
    

    If you installed the VS Code extensions referenced above, use the Testing panel to visualize & run your unit tests.

  6. Run your Next.js application locally by running:

    npm run dev
    
  7. Explore the sample application at http://localhost:3000.

  8. When you’re done, return to your terminal and stop the dev server with Ctrl-C.

Create Local Environment Variable Files

Look for these files in your project directory:

  • .env.local.template
  • env/.env.dev.local.template
  • env/.env.test.local.template
  • env/.env.test.local.template

Copy each of these files to the same location and remove the template extension from the copy.

Do not simply rename these files! Anybody who pulls your repo will need these templates to create the same files in his own local environment.

In the future, this will be accomplished with a single CLI command. (#45)

Connect to GitHub

This template supports automated release management with release-it.

If you use GitHub, create a Personal Access Token and add it as the value of GITHUB_TOKEN in .env.local.

If you use GitLab, follow these instructions and place your token in the same file.

For other release control systems, consult the release-it README.

You can now publish a release to GitHub with this command:

npm run release

Project Architecture

TODO

File Structure

TODO

NPM Scripts

TODO

Environment Variables

Next.js has a tortured relationship with environment variables and dotenv files.

Modern software applications are configuration-driven. I ought to be able to deploy the same application into my various testing and production environments, each with a unique set of configurations.

In a rational world, there are four categories of dotenv file:

Scope Secret? Example
Application No .env
Application Yes .env.local
Environment No .env.test
Environment Yes .env.test.local

The first two are application-wide, but I should be able to create as many versions of the last two as I have environments, and load them appropriately on deployment.

Non-secret files generally get pushed to the code repository, whereas secret files are preserved locally and their contents encoded into each environment’s build pipeline.

The issues:

  • Next.js only supports three such environments, which must be named development, test, and production.

  • Next.js doesn’t load these files consistently across environments.

  • Next.js has complex rules around which variables are visible where (server side or in the browser).

  • There is no easy way to get Next.js to load different dotenv files from a different location.

See the Next.js docs for more info. Meanwhile, deployment environments also get a say.

Next.js can only load the files that are actually available to it. On your local development environment, everything will work as expected. Vercel (the native Next.js platform) and AWS Amplify (also an excellent choice) expose different sets of files to the Next.js build engine at different points in the process.

Finally, there is a way to load environment variables directly into Next.js, although doing so exposes ALL such variables to the browser, instead of just the ones with the NEXT_PUBLIC_ prefix.

So it’s a rich tapestry.

This template expresses an approach that offers the following features:

  • You can define as many environments as you want and name them however you like.

  • It works in your local dev environment and works well with both Vercel and AWS Amplify.

  • Public variables are consistently visible in the browser when prefixed with NEXT_PUBLIC_, and private variables are only visible on the server side.

By way of demonstration, this template’s live dev, test, and prod demo environments integrate perfectly with my AWS API Template’s corresponding demo environments.

There are three components to this approach:

  • dotenv files
  • Next.js config
  • Build config

dotenv Files

Application-scoped dotenv files live in the main project directory. There are three of them:

File Description
.env Application settings. Syncs with the code repo.
.env.local Application secrets. Does not sync with the repo.
.env.local.template Application secrets template. Syncs with the repo. Copy it the first time you pull the repo to create an application secrets file and populate it to support local testing.

Environment-scoped dotenv files live in the env directory. There is no limit to the number and names of supported environments. Each environment requires three files, e.g. for the test environment:

File Description
.env.test Environment settings. Syncs with the code repo.
.env.test.local Environment secrets. Does not sync with the repo.
.env.test.local.template Environment secrets template. Syncs with the repo. Copy it the first time you pull the repo to create an environment secrets file and populate it to support local testing.

Next.js Config

The application-scoped dotenv files have names and locations as expected by Next.js.

When running locally, they will be loaded. When deploying remotely as part of a build process, the application secrets will not be available and must be integrated with the build process as described below.

Environment-scoped dotenv files do NOT have names or locations as expected, and Next.js will NOT pick them up. Accordingly, I’ve added code to next.config.mjs that loads these files based on an environment token passed into the ENV variable. So to run Next.js locally using the test runtime environment, you would run:

cross-env ENV=test npm run dev

Note that this ALSO loads application secrets, thus exposing them to the browser! This is not a problem because these .local files are only available locally. Application & environment secrets are loaded differently in the remote build process, and since these files are not present their contents will NOT be exposed to the browser in remote deployments.

Build Config

As a general rule, all application and environment secrets must be encoded into any remote build process. Both Vercel and AWS Amplify support build-specific environment variables, so each must be configured accordingly.

For Vercel, this is sufficient.

For Amplify, there is an additional problem: the contents of the env directory are not even available to next.config.mjs. So I’ve included an amplify.yml build script that merges all available environment variables, secret and otherwise, into the one .env file that Amplify seems to understand.

dotenv Bottom Line

Follow these rules:

  • Use the four types of file as described in dotenv Files above.

  • If you want a variable to be available in the browser, prefix its name with NEXT_PUBLIC_.

  • Encode all application & environment secrets into your build process.

  • Additionally encode an ENV variable into your build process that carries your environment token (e.g. dev, test, or prod).

  • If you are deployng to AWS Amplify, and need to add more application or environment secrets, use the pattern in the amplify.yml preBuild section to integrate your new secrets with the build.

State Model

This template relies heavily on the Redux Toolkit for application state management.

Redux Toolkit is an efficient, highly opinionated wrapper around the very popular Redux library. It’s better in every way and is the approach recommended by the Redux team.

Getting Redux Toolkit to play nicely with Next.js is not a trivial exercise and requires making some choices. This template solves those problems in an elegant way.

Redux Toolkit features the createEntityAdapter (CRA), which generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure containing instances of a particular type of data object. Think of it as a NoSQL database in your Redux state!

The front-end application doesn’t leverage CRA, but I’ve included a sample entity slice in my state model and written some tests to demonstrate ow it works.

Redux Store

TODO

Page Slice

TODO

Entity Slice

TODO

User Authentication

User authentication is enabled using NextAuth.js against an AWS Cognito User Pool.

For simplicity, we are assuming that the Cognito User Pool has its hosted UI configured. This supports a wide variety of federated login providers, including social logins like Google, Facebook, etc. See my AWS API Template for an example of how to set this up.

NextAuth can secure front-end routes (i.e. pages) and back-end routes (i.e. API endpoints). You can also pass session credentials to other resources secured by the same authentication provider, for example an AWS API Gateway route. See the demo site for an example of this in action.

Semantic UI

This template uses the Semantic UI React component set.

Your starting point is a nice reactive layout with a sticky sidebar that collapses down to a hamburger menu at mobile resolutions. There were some difficulties getting this to work properly with the installed version of Semantic UI; I’ve resolved these and commented those fixes in the code.

The Semantic toolkit is super flexible, so you can easily morph this into whatever layout works for you.

Semantic UI has a fantastic LESS-based theming system. It was a HUGE challenge getting this working properly within the Next.js context. Problem solved, though, so out of the box this template offers full Semantic UI theme support.

All aspects of the site theme can be controlled by modifying the contents of the semantic-ui directory.

Out of the box, this template leverages the Semantic UI default theme. Switch themes globally or at a component level by modifying theme.config. Override every conceivable aspect of the current theme, with full access to all related LESS variables, by editing the templates in the site directory. To examine existing themes and borrow their settings as overrides, see the contents of node_modules/semantic-ui-less/themes.

Click here to learn more about Semantic UI themes.

Page Model

TODO

Redirects

TODO

Global Redirects

TODO

Back-End Redirects

TODO

Front-End Redirects

TODO

Components

TODO

Configured Pages

TODO

Public Home Page

TODO

Private Page

The page at /private is only visible to authenticated users. This is accomplished by adding the route pattern to the config variable in middleware.js, like this:

export const config = { matcher: ['/private'] };

If an unauthenticated user attempts to access this page, he will be redirected to a login page.

Note that the link to the Private page only appears in the sidebar when the user is authenticated. This is accomplished in SidebarItems.jsx. Note that router.push does not support shallow routing to protected pages!

Coming Soon Page

If the following environment variable condition is true, the application will display a coming soon page:

process.env.NEXT_PUBLIC_COMING_SOON === '1' &&
  process.env.NEXT_PUBLIC_VERCEL_ENV !== 'preview';

If it is false, then the application will display.

In the development environment, both variables may be set explicitly in .env.development. In deployed environments, they are set explicitly in .env.production but may be overridden in your deployment pipeline.

If you are hosted at Vercel, the hosting environment will populate the NEXT_PUBLIC_VERCEL_ENV environment variable to reflect your deployment type. This value will be production on your production branch and preview on all other branches.

Unit Testing

TODO

Common Tasks

Add a Page

TODO

Add an API Route

TODO

Create & Run a Local Production Build

TODO

Analyze Your Bundles

TODO

Create a Release

TODO

Integrate a Template Update

Follow these instructions.

FAQ

Why are all your tests .jsx files?

TODO

Issues

  • Authorization fails in local prod (i.e. npm run build && npm run start in your dev environment). This behavior arises from this NextAuth issue. I’m tracking it actively.

Comments