# Nextra monorepo

# Introduction to Monorepos. Nextra as an example

📚 Monorepo Definition

A monorepo is a single repository containing multiple distinct projects , with well-defined relationships between them. One of the main reasons for using a monorepo is to have a single source of truth for all projects. This means that all projects can be kept in sync and changes can be made across all projects in a single commit. When working in JavaScript and Node.js projects, monorepos are often used to manage multiple packages that are published to npm. This is the case of Nextra.

You can see that is a monorepo for the presence of a packages directory in the root of the project containing three npm related packages:

➜  nextra git:(casiano) ✗ tree -L 1 packages
packages
├── nextra
├── nextra-theme-blog
└── nextra-theme-docs
1
2
3
4
5

Also you can see in the Nextra package.json that the project depends on the npm module changesets (opens new window), which is also a monorepo!.

📚 Changesets

Changesets is a concept that hold two bits of information:

  1. A version type (following semver (opens new window)), and
  2. Change information to be added to a changelog.

In a monorepo context, changesets will handle bumping dependencies of changed packages.

There is a configuration file .changeset/config.json that specifies the packages that are part of the monorepo:

➜  nextra git:(casiano) ✗ cat .changeset/config.json 
{
  "$schema": "https://unpkg.com/@changesets/config@2.0.1/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [["nextra", "nextra-theme-docs", "nextra-theme-blog"]],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
    "onlyUpdatePeerDependentsWhenOutOfRange": true
  },
  "ignore": ["example-blog", "example-docs", "swr-site", "docs"]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

The overall changeset cycle after initialization should lead is a loop that looks like:

  1. Changesets added along with each change
  2. The version command is run when a release is ready, and the changes are verified
  3. The publish command is run afterwards.

See the changesets glossary (opens new window).

Watch the video below Version Your Packages with Changesets:

Try to reproduce the steps in the video in the Nextra monorepo.

# Workspace Dependencies in the package.json

If you look at the package.json of nextra, you find this line:

   ...
 "devDependencies": {
    "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
     ...
    "nextra": "workspace:*",
    ...
    "vitest": "^2.0.3"
  },
 ...
}
1
2
3
4
5
6
7
8
9
10

The "workspace:*" syntax in the package.json file is related to workspace management in monorepos. Let me explain its meaning and significance:

  1. Workspace Protocol: The workspace: prefix is known as the "workspace protocol" . It's used in monorepo setups to reference other packages within the same repository.
  2. Version Specifier: The * after workspace: is a version specifier. In this case, it means "use the version of the package that exists in the current workspace" .
  3. Local Package Reference: "nextra": "workspace:*" indicates that this package depends on another package named "nextra" that exists within the same monorepo workspace .It will use whatever version of nextra is currently in the workspace, making it easier to develop and test changes across multiple packages simultaneously.
  4. Flexible Versioning: Using workspace:* allows for more flexible versioning within a monorepo. It ensures that the package always uses the latest version of the dependency from the workspace, rather than a specific pinned version .
  5. Build Tools Integration: This syntax is supported by modern JavaScript package managers like Yarn (v2+) and pnpm, which are designed to work efficiently with monorepos .
  6. Development Workflow: It's particularly useful during development in a monorepo, as it allows you to work on multiple packages simultaneously without needing to manually update version numbers or publish packages to test changes.

# pnpm Workspaces and Nx

# Instructions

See and follow the instructions in the README.md (opens new window) in the branch casiano at the fork of the Nextra monorepo (opens new window). The teacher's fork is at gh-cli-for-education/nextra/tree/casiano (opens new window).

Make your own fork of the teacher's fork in your personal GitHub account.

# Installation. Turborepo and PNPM Workspaces

The Nextra repository uses PNPM Workspaces (opens new window) and Turborepo (opens new window). To install dependencies, run pnpm install in the project root directory.

For me, the installation worked with node v22.2.0 but not with v23.0.0:

➜  nextra-theme-docs git:(casiano) ✗ node --version
v22.2.0
1
2

# Turborepo

Turborepo uses remote cache stores (opens new window) via providers like Vercel (opens new window) to cache the result of tasks like npm run build, so that CI tasks avoid to do the same work twice. Other tasks are test, lint... It parallelizes work across all available cores. It uses the package.json scripts, the dependencies you've already declared, and a single turbo.json file.

➜ nextra git:(casiano) ✗ cat turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": [
        // Run `build` in workspaces I depend on first
        "^build"
      ],
      "outputs": ["dist/**", ".next/**"]
    },
    "build:tailwind": {
      "dependsOn": [],
      "outputs": ["style.css"]
    },
    "test": {
      "outputs": ["dist/**"]
    },
    "types:check": {
      "dependsOn": [
        // Run `build` in workspaces I depend on first
        "^build"
      ],
      "outputs": ["dist/**", ".next/**"]
    },
    "clean": {
      "cache": false
    },
    "dev": {
      "dependsOn": [
        // Run `build` in workspaces I depend on first
        "^build"
      ],
      "cache": false
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# pnpm Workspaces

pnpm has built-in support for monorepositories (opens new window). You can create a workspace to unite multiple projects inside a single repository. A workspace must have a pnpm-workspace.yaml file in its root. A workspace also may have an .npmrc in its root.

➜  nextra git:(casiano) ✗ cat pnpm-workspace.yaml 
packages:
  - packages/*
  - examples/*
  - docs
1
2
3
4
5
➜  nextra git:(casiano)cat .npmrc 
strict-peer-dependencies=false
shell-emulator=true
1
2
3

# Installation

To install dependencies, run pnpm install in the project root directory.

# Build Nextra Core

cd packages/nextra
pnpm build
1
2

Watch mode: pnpm dev

# Build Nextra Theme

cd packages/nextra-theme-docs
pnpm build
1
2

# Development

# Running the package nextra in watch mode

Set nvm use v22. I went to the packages/nextra directory and run pnpm dev. It uses tsup (opens new window) a utility to bundle TypeScript code.

# Running the package nextra-theme-docs in watch mode

I opened a new terminal; set nvm use v22 and then went to the packages/nextra-theme-docs directory.

➜  nextra git:(casiano)cd packages/nextra-theme-docs 
1

I run pnpm dev. It concurrently runs

  • tsup --watch and
  • TAILWIND_MODE=watch pnpm postcss css/styles.css -o dist/style.css --watch.

Here are the scripts in the package.json file:

➜ nextra-theme-docs git:(casiano) ✗ jq '.scripts' package.json

{
  "build": "tsup",
  "build:all": "pnpm build && pnpm build:tailwind",
  "build:tailwind": "pnpm postcss css/styles.css -o dist/style.css --verbose",
  "clean": "rimraf ./dist ./style.css",
  "dev": "concurrently \"pnpm dev:layout\" \"pnpm dev:tailwind\"",
  "dev:layout": "tsup --watch",
  "dev:tailwind": "TAILWIND_MODE=watch pnpm postcss css/styles.css -o dist/style.css --watch",
  "prepublishOnly": "pnpm build:all",
  "test": "vitest run",
  "types": "tsup --dts-only",
  "types:check": "tsc --noEmit"
}
1
2
3
4
5
6
7
8
9
10
11
12
13

The table below shows the description of some of the available scripts:

Command Description
pnpm dev Watch mode
pnpm dev:layout Watch mode (layout only)
pnpm dev:tailwind Watch mode (style only)

# Running the example docs in watch mode

I opened a new terminal; set nvm use v22 and then went to examples/docs and run:

➜  docs git:(casiano) ✗ pnpm dev

> example-docs@ dev /Users/casianorodriguezleon/campus-virtual/2223/learning/nextjs-learning/nextra-learning/nextra/examples/docs
> next

⚠ Port 3000 is in use, trying 3001 instead.
⚠ Port 3001 is in use, trying 3002 instead.
⚠ Port 3002 is in use, trying 3003 instead.
  ▲ Next.js 15.0.1
  - Local:        http://localhost:3003

✓ Starting...
  automatically enabled Fast Refresh for 2 custom loaders
✓ Ready in 4.5s
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Any changes to example/docs will be re-rendered instantly.

Here we have used the watch mode for both nextra and the theme in separated terminals. Otherwise, if you update the core or theme packages, a rebuild is required.

# Watching the propagation of the changes

Now, go to file packages/nextra/src/client/components/callout.tsx and change the TypeToEmoji object and modify the warning emoji for warning to '👀' (eye emoji).

packages/nextra/src/client/components/callout.tsx

const TypeToEmoji = {
  default: '💡',
  error: '🚫',
  info: <InformationCircleIcon className="_mt-1" />,
  warning: ' 👀' // '⚠️'
}
1
2
3
4
5
6

Now in the file /examples/docs/src/pages/themes/docs/callout.mdx you have an example of usage of the callout component with the type attribute set to warning:

### Warning

<Callout type="warning">This API will be deprecated soon.</Callout>

```mdx
<Callout type="warning">This API will be deprecated soon.</Callout>
```
1
2
3
4
5
6
7

When we go to the browser to somehting like http://localhost:3002/themes/docs/callout and refresh the page we see the eyes emoji in the warning callout:

warning callout

# pnpm workspaces

See chapter pnpm Workspaces

Last Updated: a few seconds ago