Skip to content

chl03ks/nextjs-monorepo-example

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

556 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Monorepo concepts, tips and tricks oriented around NextJs

build Codefactor Maintainability Techdebt Codacy grade LoC TS Licence

WARNING This document covers the most recent version based on Yarn 3.0 and NextJs 10.2.1. Docs and examples are still WIP.

Useful to

  • Establish a structure and show a lifecycle perspective (dx, ci/cd...)
  • How to create shared packages, shared locales, assets, images folders, api types...
  • Integrate tools & configs (ts, jest, changelogs, versioning...).
  • Clarify some advantages of monorepos (team cohesion, consistency, duplication...).
  • Create nextjs/vercel/prisma/webpack5... bug reports with reproducible examples (initial goal of this repo).

The approach doesn't rely on monorepo tools such as Rush or Nx. It does not try to compete, accent is on recipes with a focus on workspace enabled package managers like yarn 3.0, pnpm, npm v7... By keeping the examples as agnostic as possible, it should be very easy to apply them in others tools. See also the FAQ about differences.

Open in Gitpod

1. Structure

All in typescript, latest nextjs 10.2+, webpack5, yarn v3, ts-jest, prettier, eslint, emotion, tailwind, prisma 2... add as much as you like.

Two apps

Some shared code

Static shared assets

Locales, images, ... can be shared by using symlinks in the repo.

  • See the global static folder.

Folder overview

.
├── apps
│   ├── blog-app                 (NextJS SSG app)
│   │   ├── public/
│   │   │   └── shared-assets/   (symlink to global static/assets)
│   │   ├── src/
│   │   ├── CHANGELOG.md         (autogenerated with changesets)
│   │   ├── jest.config.js
│   │   ├── next.config.js
│   │   ├── package.json         (define package workspace:package deps)
│   │   └── tsconfig.json        (define path to packages)
│   │
│   └── web-app                  (NextJS app with api-routes)
│       ├── public/
│       │   ├── shared-assets/   (symlink to global static/assets)
│       │   └── shared-locales/  (symlink to global static/locales)
│       ├── src/
│       ├── CHANGELOG.md         (autogenerated with changesets)
│       ├── jest.config.js
│       ├── next.config.js
│       ├── package.json         (define package workspace:package deps)
│       └── tsconfig.json        (define path to packages)
│
├── packages
│   ├── core-lib                 (basic ts libs)
│   │   ├── src/
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── main-db-prisma          (basic db layer with prisma)
│   │   ├── prisma/
│   │   ├── src/
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── ui-lib                  (basic design-system in react)
│       ├── src/
│       ├── CHANGELOG.md
│       ├── package.json
│       └── tsconfig.json
├── static                       (no code: images, json, locales,...)
│   ├── assets
│   └── locales
├── .yarnrc.yml
├── docker-compose.yml           (database service for now)
├── package.json                 (the workspace config)
└── tsconfig.base.json           (base typescript config)

2. Howtos ?

2.1 How create a new shared package ?

  1. Workspace config lives in the root package.json, see workspace section. there's already 2 roots defined: ./packages/_ and ./apps/_. So nothing to do.

  2. Create a new folder, i.e: mkdir packages/magnificent-poney.

  3. Initialize a package.json, set a name and dependencies you'll need. For inspiration, take the ui-lib as an example. Copy/paste other files you might need (tsconfig.json...). Place sources in the magnificent-poney/src folder.

  4. To use it in an app first declare the dependency in its package.json deps by adding "@your-org/magnificent-poney": "workspace:*". Inspiration in web-app/package.json.

  5. Run yarn install to update the workspace and create symlinks.

  6. Add tsconfig paths in the app tsconfig.json, take an example in web-app/tsconfig.json

    {
       "compilerOptions": {
         "baseUrl": "./src",
         "paths": {
           // regular app aliases
           "@/components/*": ["./components/*"],
           // packages aliases, relative to app_directory/baseUrl
           "@your-org/magnificent-poney/*": ["../../../packages/magnificent-poney/src/*"],
           "@your-org/magnificent-poney": ["../../../packages/magnificent-poney/src/index"]
         },
    }

    PS: The packages aliases should be declared per app (not in the tsconfig.base.json), so to keep being explicit with the dependencies.

  7. Be sure your next.config.js app overrides webpack like in nextjs.config.js:

    webpack: function(config, { defaultLoaders }) {
       // Will allow transpilation of shared packages through tsonfig paths
       // @link https://github.com/vercel/next.js/pull/13542
       const resolvedBaseUrl = path.resolve(config.context, '../../');
       config.module.rules = [
         ...config.module.rules,
         {
           test: /\.(tsx|ts|js|jsx|json)$/,
           include: [resolvedBaseUrl],
           use: defaultLoaders.babel,
           exclude: (excludePath) => {
             return /node_modules/.test(excludePath);
           },
         },
       ];
       return config;
     }

    PS:

  8. Using the package in your app

    The packages are now linked to your app, just import them like regular packages: import { poney } from '@your-org/magnificent-poney'.

  9. Optional package publishing.

    If you need to share some packages outside of the monorepo, you can publish them to npm or private repositories. An example based on microbundle is present in each package. Versioning and publishing can be done with atlassian/changeset, and it's simple as typing:

    $ yarn changeset

    Follow the instructions... and commit the changeset file. A "Version Packages" P/R will appear after CI checks. When merging it, a github action will publish the packages with resulting semver version and generate CHANGELOGS for you.

    PS:

    • Even if you don't need to publish, changeset can maintain an automated changelog for your apps. Nice !
    • To disable automatic publishing of some packages, just set "private": "true" in their package.json.
    • Want to tune the behaviour, see .changeset/config.json.

3. Monorepo essentials

3.1 Monorepo scripts

Some convenience global scripts are defined in the root package.json, they generally call their counterparts defined in packages and apps.

{
  scripts: {
    // Global workspaces commands, ie:
    clean: 'yarn workspaces foreach -ptv run clean',
    typecheck: 'yarn workspaces foreach -ptv run typecheck',
    lint: 'yarn workspaces foreach -ptv run lint',
    test: "run-s 'test:*'",
    'test:unit': 'yarn workspaces foreach -ptv run test:unit',
    // Manage versions and releases with atlassion/changesets
    changeset: 'changeset',
    release: 'yarn build && changeset publish',
    // Utility script to check/upgrade deps across the entire monorepo
    'deps:check': 'npm-check-updates --deep --dep prod,dev,optional',
    'deps:update': 'npm-check-updates -u --deep --dep prod,dev,optional',
    // Some extras you can set based on needs.
    'apps:build': "yarn workspaces foreach -ptv --include '*-app' run build",
    'packages:build': "yarn workspaces foreach -ptv --include '@your-org/*' run build",
  },
}

PS:

  • Convention: whatever the script name (ie: test:unit), keeps it consistent over root commands, packages and apps.
  • The use of yarn workspaces commands can be replicated in pnpm, nmp7+lerna...

3.2 Maintaining deps updated

The commands yarn deps:check and yarn deps:updates will help to maintain the same versions across the entire monorepo.

4. Quality

4.1 Linters

An example

4.2 Hooks / Lint-staged

Check the .husky folder content to see what hooks are enabled.

4.2 Tests

4.3 CI

You'll find some example workflows for github action in .github/workflows. By default, they will ensure that

  • You don't have package duplicates.
  • You don't have typecheck errors.
  • You don't have linter / code-style errors.
  • Your test suite is successful.
  • Your apps (nextjs) or packages can be successfully built.

Each of those steps can be opted-out.

To ensure decent performance, those features are present in the example actions:

  • Caching of packages (node_modules...) - install around 25s

  • Caching of nextjs previous build - built around 20s

  • Triggered when changed using actions paths, ie:

     paths:
       - "apps/blog-app/**"
       - "packages/**"
       - "package.json"
       - "tsconfig.base.json"
       - "yarn.lock"
       - ".yarnrc.yml"
       - ".github/workflows/**"
       - ".eslintrc.base.json"
       - ".eslintignore"
    

8. Deploy

Vercel

Vercel support natively monorepos, see the vercel-monorepo-deploy document.

Others

Netlify, aws-amplify, k8s-docker, serverless-nextjs recipes might be added in the future. PR's welcome too.

FAQ

Quid next-transpile-modules ?

And why this repo example doesn't use it to support package sharing.

next-transpile-modules is one of the most installed packages for nextjs. It basically allows you to transpile some 3rd party packages present in your node_modules folder. This can be helpful for transpiling packages for legacy browser support (ie11), esm packages (till it lands in nextjs) and handle shared packages.

In this repo, we use next-transpile-modules only for ie11 and esm. The monorepo management is done through tsconfig path. It will work best regarding to external tooling (ts-jest...), but comes with some limitations if your shared package use an scss compiler for example. Note that future version of NextJs might improve monorepo support through experimental.externalDir option.

See here a quick comparison:

Support matrix tsconfig paths next-transpile-module
Typescript
Javascript
NextJs Fast refresh
CSS custom webpack cfg
SCSS custom webpack cfg
CSS-in-JS
ts-jest custom aliases
Vercel monorepo
Yarn 2 PNP
Webpack5
Publishable (npm) ❌ (ntm relies on "main")

About

A nextjs monorepo example with typescript, ts-jest and yarn workspaces

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • TypeScript 64.4%
  • JavaScript 30.6%
  • Dockerfile 2.9%
  • Shell 2.1%