Skip to main content

@ensono-stacks/next

The next plugin contains generators to augment existing NextJs projects. Adding eslint rules, testing config as well as installing and configuring NextAuth.js in your NextJs app.

Using a standard setup for your Next app ensures consistency and code quality across multiple applications quickly. NextAuth (alongside Redis) can also be quickly added to a project without costly configuration and setup.

Using the infrastructure generator you can setup your application with the necessary infrastructure config to host it in k8s, with optional OpenTelemetry auto instrumentation.

Prerequisites

An existing Next application. Note the generator will fail if run in an empty workspace with no applications. To create a new Next application please run the NX Next generator with the following command including any relevant options. See @nrwl/next plugin docs

nx g @nrwl/next:app my-new-app

Setting up @ensono-stacks/next

Install the @ensono-stacks/next with the following command:

npm install --save-dev @ensono-stacks/next@latest

Executors and Generators

To see a list of the plugin capabilities run the following command:

nx list @ensono-stacks/next

View additional information about a plugin capability through the following command:

nx g @ensono-stacks/next:[generator-executor-name] --help

Generators

@ensono-stacks/next:init

Adds custom config and developer tools to an existing next application

The next init generator will add a custom ESlint config to an existing NextJs application, install eslint-plugin-testing-library to the project. as well as update project.json with a custom test config to allow coverage collection from jest.

If infrastructure already exists for the workspace, this plugin will also call @ensono-stacks/next:infrastructure to add build & deploy infrastructure to the Next project.

Prerequisites

An existing Next application

Usage

nx g @ensono-stacks/next:init

Command line arguments

The following command line arguments are available:

OptionDescriptionTypeAccepted ValuesDefault
--projectName of the existing next applicationstringnameOfApplicationN/A
--infraAdd build & deploy infrastructure to the Next projectbooleantrue/falsetrue

Generator Output

The following files will be updated if no workspace infrastructure information is present, or if the --infra flag is set to false

UPDATE apps/baseline-next-app/project.json #Updated with custom test config to allow for coverage collection
UPDATE apps/baseline-next-app/.eslintrc.json #Ehanced with additional linting rules
UPDATE apps/baseline-next-app/tsconfig.json #Minor enhancements
UPDATE apps/baseline-next-app/tsconfig.spec.json #Updates for monorepo structure
UPDATE apps/baseline-next-app/specs/index.spec.tsx #Formatting changes

Otherwise, the @ensono-stacks/next:infrastructure will have also added build & deploy infrastructure to the Next project.

@ensono-stacks/next:next-auth

Add Next Authentication to your next application

The next-auth generator will install and configure NextAuth.js into an existing Next application. It will add the initial configuration, add the session provider, setup an API endpoint and add local environmental variables. It will also configure provider specific setup.

Prerequisites

An existing Next application

Usage

nx g @ensono-stacks/next:next-auth

Command line arguments

The following command line arguments are available:

OptionDescriptionTypeAccepted ValuesDefault
--projectThe name of the projectnameOfApplicationstringN/A
--providerThe provider to be installedstringnone/azureAdnone
--skipPackageJsonDo not add dependencies to package.jsonbooleantrue/falsefalse

Generator Output

  • Creates a new Next API endpoint with the file name [...nextauth].ts. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations. If you have specified a provider when running the generator this will be added to the providers array
/apps/appName/pages/api/[...nextauth].ts
import NextAuth from 'next-auth';
import AzureADProvider from 'next-auth/providers/azure-ad';
const nextAuth = NextAuth({
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
],
});
export default nextAuth;
  • Install the next-auth package and add to package.json, unless the --skipPackageJson option was used
/package.json
"dependencies": {
...otherDependencies
"next-auth": "4.18.8",
},
  • Create or append an .env.local file. Adding required next auth environmental variables. These will vary depending on the provider chosen.
/.env.local
NEXTAUTH_URL=http://localhost:4200
NEXTAUTH_SECRET=secretValue
AZURE_AD_CLIENT_ID=
AZURE_AD_CLIENT_SECRET=
AZURE_AD_TENANT_ID=
note

Be sure to update the environmental variables with the values provided by your provider

/apps/appName/_app.tsx
import { AppProps } from 'next/app';
import Head from 'next/head';
import './styles.css';
import { SessionProvider } from 'next-auth/react';
function CustomApp({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
<SessionProvider session={session}>
<Head>
<title>Welcome to testing!</title>
</Head>
<main className="app">
<Component {...pageProps} />
</main>
</SessionProvider>
);
}
export default CustomApp;

From here with the configuration complete it is now possible to access the useSession hook from next auth. For further information please see the Getting Started Guide to Next Auth

@ensono-stacks/next:next-auth-redis

Add Redis for session management to your Next application

The next-auth-redis generator will add Redis for session management into your existing Next app with Next-auth.

Prerequisites

An existing Next application with Next-auth. Use the @ensono-stacks/next:next-auth generator to add this into your application

Usage

nx g @ensono-stacks/next:next-auth-redis

Command line arguments

The following command line arguments are available:

OptionDescriptionTypeAccepted ValuesDefault
--projectThe name of the projectstringstringN/A
--adapterNameName of the generated Redis adapter librarystringnext-auth-redis
--envVarName of the env var that stores connection string for RedisstringREDIS_URL

Generator Output

A new redis library will be added to your libs folder with the following structure:

libs
β”‚ next-auth-redis
β”‚ β”œβ”€β”€ src
β”‚ β”‚ β”œβ”€β”€ index.ts #All code required for session management with Redis
β”‚ β”‚ β”œβ”€β”€ index.test.ts #Unit tests using 'ioredis-mock' to mock Redis functions.
β”‚ β”œβ”€β”€ README.md
β”‚ β”œβ”€β”€ tsconfig.json
β”‚ β”œβ”€β”€ tsconfig.lib.json
β”‚ β”œβ”€β”€ project.json
β”‚ β”œβ”€β”€ .eslintrc.json
β”‚ β”œβ”€β”€ jest.config.ts
└── └── tsconfig.spec.json

In order for Redis to be used within next-auth a new entry for the redis library is added to the tsconfig.base.json "paths"

"paths": {
"@<workspace-name>/next-auth-redis": [
"libs/next-auth-redis/src/index.ts"
]
}

Your [...nextauth].ts file within the Next application will be updated to use the new Redis adapter:

import { Redis } from 'ioredis';
import NextAuth from 'next-auth';
import AzureADProvider from 'next-auth/providers/azure-ad';

import { IORedisAdapter } from '@0-5-23-next-with-test-app/next-auth-redis';

const nextAuth = NextAuth({
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
],
adapter: IORedisAdapter(new Redis(process.env.REDIS_URL)),
});

export default nextAuth;

Build and Deployment updates

When infrastructure is detected for the application, these files will be enhanced to cater for Redis:

  • "app-name"/build/values[-prod].yaml files will have 3 new entries added for redis
redisURL: ''
nextAuthSecret: ''
nextAuthURL: <app-name>.<internal/external domain>
  • "app-name"/terraform/main.tf will have a new azurerm_redis_cache resource added. The variables.tf file will have these corresponding variables defined
resource "azurerm_redis_cache" "default_primary" {
name = var.redis_name
location = var.redis_resource_group_location
resource_group_name = var.redis_resource_group_name
capacity = var.redis_capacity
family = var.redis_family
sku_name = var.redis_sku_name
minimum_tls_version = var.minimum_tls_version
}
  • "app-name"/terraform/[prod/nonprod].tfvars will have additional variables added.
redis_name                    = "<company>-<domain>-<prod/nonprod>-<cloud region>-<business component>"
redis_resource_group_location = "%REPLACE%"
redis_resource_group_name = "<company>-<domain>-<prod/nonprod>-<cloud region>-<business component>"
warning

Be sure to update the redis_resource_group_location value

  • "app-name"/terraform/outputs.tf will have the redis_connection_string added
output "redis_connection_string" {
sensitive = true
value = "rediss://:${azurerm_redis_cache.default_primary.primary_access_key}@${azurerm_redis_cache.default_primary.hostname}:${azurerm_redis_cache.default_primary.ssl_port}"
}
  • "app-name"/.env.local will have the REDIS_URL env variable added and set
REDIS_URL=localhost:6379
  • "app-name"/project.json will have the helm-upgrade commands updated to use the NEXTAUTH_SECRET
"helm-upgrade": {
"executor": "nx:run-commands",
"options": {
"commands": [
{
"command": "helm upgrade [... unchanged ...] --set nextAuthSecret=\\\"$NEXTAUTH_SECRET\\\"",
"forwardAllArgs": false
}
],
"cwd": "apps/baseline-next-app/build/terraform"
},
"configurations": {
"prod": {
"commands": [
{
"command": "helm upgrade [... unchanged ...] --set nextAuthSecret=\\\"$NEXTAUTH_SECRET\\\"",
"forwardAllArgs": false
}
]
}
}
warning

For Azure DevOps, the NEXTAUTH_SECRET needs to be added to the <company>-<component>-<domain>-nonprod and <company>-<component>-<domain>-prod' variable groups

@ensono-stacks/next:infrastructure

Configure Infrastructure for your Next project

The infrastructure generator will provide all the necessary tools and setup ready to host your application in a Kubernetes Cluster. You can also choose to opt in to OpenTelemetry auto instrumentation.

Prerequisites

An existing Next application. This may already exist if you agreed to install the infra during next:init generator.

Usage

nx g @ensono-stacks/next:infrastructure

Command line arguments

The following command line arguments are available:

OptionDescriptionTypeAccepted ValuesDefault
--projectThe name of the projectnameOfApplicationstringN/A
--openTelemetryAdd OpenTelemetry auto instrumentationbooleantrue/falsefalse

Generator Output

β”œβ”€β”€ workspace root
β”œβ”€β”€ apps
β”œβ”€β”€ myapp
β”œβ”€β”€ build
β”œβ”€β”€ helm
β”œβ”€β”€ terraform
  • Creates numerous files under the two folders, helm and terraform. You can then go in and update relevant parts for your use case.

  • Adds following files to .gitignore

'**/.terraform/*',
'*.tfstate',
'*.tfstate.*',
'crash.log',
'crash.*.log',
'override.tf',
'override.tf.json',
'*_override.tf',
'*_override.tf.json',
'.terraformrc',
'terraform.rc',
  • installs following dev dependencies
@nx-tools/nx-container
@nx-tools/container-metadata
@jscutlery/semver
  • It is a requirement for the stacks object to exist inside nx.json, as this is read to know how to scaffold the infrastructure as code values. This object will already be populated by this point via the previous project scaffolding steps.
"stacks": {
"business": {
"company": "Amido",
"domain": "stacks",
"component": "nx"
},
"domain": {
"internal": "test.com",
"external": "test.dev"
},
"cloud": {
"platform": "azure",
"region": "euw"
},
"pipeline": "azdo",
"terraform": {
"group": "terraform-group",
"storage": "terraform-storage",
"container": "terraform-container"
},
"vcs": {
"type": "github",
"url": "remote.git"
}
}

Understanding the Infrastructure

Azure devops configuration exists within the build folder for each new generated app project. This folder lives at root.

build/azDevOps

azuredevops-runner.yaml

Here you will find the actions for triggering the pipelines. Basically, creating a PR will build as a non prod artefact and merging into main branch will build as a prod artefact, with the relevant parameter specified.

azuredevops-stages.yaml

This is of course the actual stages of the pipeline that are configured. Most of the detail is done via taskctl, which can found as the last task in the build job.

taskctl

taskctl has been used to enable across different environments and builds. Cross platform, one single syntax.

As a rule of thumb, each task here references a target execution via Nx defined inside project.json. The flag --target is used to pass in the appropriate value.

build/taskctl/tasks.yaml

helm:
description: Lint Helm Charts
command:
- npx nx affected --base="$BASE_SHA" --target=helm-lint

apps/myapp/project.json

"helm-lint": {
"executor": "nx:run-commands",
"options": {
"commands": [
{
"command": "helm lint",
"forwardAllArgs": false
}
],
"cwd": "apps/myapp/build/helm"
}
}

Hence, running the following will trigger the intended execution. The pipeline takes care of this for us.

npx nx affected --base="$BASE_SHA" --target=helm-lint

Following on from this, we can see various steps such as linting, building, running helm, versioning and terraform are subsequently executed.

Helm

The configuration files for Helm Charts live inside the build folder under directory for your app, within the project

myproject/apps/myapp/build/helm

In the infra pipeline, the steps for Helm will begin by linting, followed by either an upgrade or install. If the Helm chart is already installed, then an upgrade occurs based on the given command. If it isn't installed, then an installation occurs instead. The command accepts a --atomic flag which will allow Helm to roll back to the previous release should a failure during upgrade occur. On install, this would cause the installation to fail if there were any issues.

The remaining tasks are then carried out post versioning, covered in the next section.

Versioning

jscutlery:semver is an Nx plugin which has been configured to automate semantic versioning and release in these projects. It follow conventional commits and is also applied to proceeding pipeline targets such as Helm charts.

Package & Push

After versioning, our build is containerised using Docker and pushed to the set Azure registry.

Likewise, the Helm Charts are also packaged and pushed to their respective place in the Azure registry.

Finally a Github release is tagged with relevant notes using jscutlery.

Terraform

This is the last group of tasks to run as part of the infrastructure. See myproject/apps/myapp/build/terraform for configuration files.

One thing to highlight is that once the Terraform apply task is completed, a Helm install will also be executed. As mentioned earlier, the default behaviour is to deploy a non-production instance when a PR is created and once the PR is merged, then the deployment is made to production.

OpenTelemetry

OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyse your software’s performance and behaviour.

If the generator is used with the openTelemetry option it will add auto instrumentation to the pods, and the application will start exporting default node metrics and traces.

podAnnotations:
instrumentation.opentelemetry.io/inject-nodejs: 'true'
caution

OpenTelemetry logs are in an experimental phase, this means there is no node support at the moment, and there is no known ETA either.