Deploying a Nuxt Site on Netlify between a pnpm monorepo
Tags: nuxt, webdev, pnpm, tutorial
Published: 2022-07-06 08:05:00

I've been walking through the fine line between over-engineering and learning new integration possibilities with my portfolio crafting. 

Because, yes I'm one of those developers who like to complicate his life for the bigger purpose of learning and "probably" implementing this knowledge in future projects.

![Overengineering a napkin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjo2q40r8wfsyk6m5p27.gif)
 
Today's articles explain how to successfully deploy a static site with [Nuxt v3.0.0](https://v3.nuxtjs.org/) between a [pnpm monorepo](https://pnpm.io/workspaces) on Netlify.

For this tutorial, I'm assuming you have a basic understanding of monorepos, npm packages, deploying apps to Netlify, and a little bit of CI/CD. If this list sounded like phrases coming from a Chewbacca to you, consider investigating a little before coming back here to properly get some value out of it.

It sounds very specific, but if you find yourself in the same use-case, this is the article for you.

You can find the code for this repo here https://github.com/alvarosabu/pnpm-monorepo-nuxt

## The initial idea

Before you think I'm crazy, this is what I wanted to achieve:

![Monorepo structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/enojlsd7qoyn7c01w95b.png)

Sounds simple, a `packages` folder containing different libs such as a UI library with my design system done in VueJS, a Typescript lib with utilities and Portfolio website below an `apps` directory made with [Nuxt v3](https://v3.nuxtjs.org/) that will consume several resources from the packages between the monorepo.

I liked the idea of being able to create the UI and utils packages on the road and test them directly on the portfolio without the need for extra playgrounds. Then being able to publish and release **npm packages** from them to install it in other projects easily.

> If you are not planning to publish multiple packages (public or private) then a **monorepo** might not be the answer for you. A simple repo with a good structure would be enough.

## Choosing PNPM workspaces

At the moment I started to transform this draft of an idea into an actual thing, and did some investigation on which monorepo technologies would suit me the most.

I started with [Lerna](https://github.com/lerna/lerna) in the early phases, I was using it with **yarn** workspaces and the whole CI/CD process was pretty smooth using [Semantic Release](https://github.com/semantic-release/semantic-release). 

On the road, I discover [pnpm](https://pnpm.io/) as a faster alternative to npm/yarn, also several of my favorite OSS projects were using it (Vite, Vueuse, etc), I decided to give it a try.

Then Lerna announced its temporally archive due to lack of maintenance this year by its lead maintainer, later to being taken over stewardship by [Nrwl](https://nrwl.io/), check the announcement [here](https://github.com/lerna/lerna/issues/3121)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1mzd8vq2uimonfjjajm.png)

Before that announcement, several users of Lerna (including me) were looking for an alternative that allowed us to easily create and maintain a monorepo of these characteristics. 

Since I was using pnpm, I found out that it has a built-in support for monorepos by creating a [Workspace](https://pnpm.io/workspaces). There it was the solution that I needed.

![Pnpm workspace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8cp38l0282al6axtgch.png)
 
All you need to do is add a `pnpm-workspace.yaml` with the following content:

```yaml
packages:
  # all packages in subdirs of packages/ and app/
  - 'packages/**'
  - 'apps/**'
```

And tweak your `package.json` to add this:

```json
// package.json

{
  ...
  "workspaces": [
    "apps/**",
    "packages/*"
  ],
  ...
}
```

We're ready to move to the next step

## Nuxt

![Nuxt3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w46l7gp11p0jshrvvmpp.png)

Being a VueJS and Nuxt fanboy as I'm 🤪, jokes aside, is the framework find myself enjoying the most when coding. It wasn't a difficult choice for me to do my Portfolio with a recently stable [Nuxt v3.0.0-rc](https://v3.nuxtjs.org/).

For those of you that are not familiar with The Nuxt ecosystem, is a hybrid Vue framework that allows you to easily create SSR, CSR or SSG experiences with an amazing DX (Developer Experience)

To get started just run this command inside of the `apps` directory:

```bash
pnpm dlx nuxi init portfolio
```

When installing the dependencies, is important to use the `shamefully-hoist` flag. (I don't really fully understand why, better explained [here](https://pnpm.io/npmrc#shamefully-hoist))

```
pnpm install --shamefully-hoist
```

And voilá, by running `pnpm run dev` this beautiful welcome screen showed up in my browser:

![Nuxt Welcome Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spq6a7khtos9i1s1tpj9.png)

Before we jump into the Netlify config there is a final thing we need to set on our brand new Nuxt. Since I want to generate the project statically I will use `nuxt generate`

## Netlify

This is the part where things start to get interesting 🤭.

[Netlify](https://www.netlify.com/) is a cloud computing platform that offers hosting and serverless backend services for web apps and static websites.

I personally use it a lot and to be completely fair, **I have never used it for such complex integration**. Most of the time was to deploy sites for clients, one or another reproduction, slides for a conference talk, some OSS libraries documentation under my domain, you know, easy stuff.

So following the [Netlify's documentation for monorepos](https://docs.netlify.com/configure-builds/monorepos/) we can add a `netlify.toml` file on the root of the repository with the `[build].base` pointing to the portfolio directory `apps/blog` like this:

```toml
[build.environment]
  NODE_VERSION = "16"
[build]
  base = "apps/blog/"
```

Then we need a `publish` directory so Netlify knows where the deploy-ready HTML files and assets generated by the build are located.

Whenever we run `nuxt generate`, it will prompt the following:

![Nuxt generate output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0uidmjbn175fx6jdot0n.png)
 
It seems we found our directory `.output/public`, we can update the `netlify.toml` to:

```
[build.environment]
  NODE_VERSION = "16"
[build]
  base = "apps/blog/"
  publish = ".output/public"
```
🤔 That should work right?

The next thing to set is the build command. This was the moment I realized that **Netlify does not support PNPM right away yet**  (at the time of writing there is a [Feature Request open](https://github.com/netlify/build/issues/1633)).

🥲 The world seemed to collapse in front of my eyes (drama), Oh no! I will need to get back to `yarn`, or even worse, to bare `npm` 😭 (more drama).

I stopped and thought, what would my heroes do at this moment? 

![My Vite hero](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0h2fttkdojb9yqs6j8s.png)
 
Yeeeess!! Let see how [Vite netlify config](https://github.com/vitejs/vite/blob/main/netlify.toml) for docs was looking like and BAAAM 💥 there was the answer to our needs:

```toml
[build.environment]
  NODE_VERSION = "16"
  NPM_FLAGS = "--version" # prevent Netlify npm install
[build]
  base = "apps/blog/"
  publish = ".output/public"
  command = "npx pnpm i --store=node_modules/.pnpm-store --frozen-lockfile && npx pnpm run generate"
```

We added a `NPM_FLAGS` property to prevent Netlify to install packages with `npm` by default.  Then we use `npx` to install pnpm and then run any command that we want. 

And don't forget the redirects:

```toml
[[redirects]]
  from = "/*"
  to = "index.html"
  status = 200
```

Let's see what happens... 🤔

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn2fc28clahzm084o7hd.png)

If we push and check the deployment on Netlify, we will see that is complaining about `Failed to resolve entry for package "@pnpm-monorepo-nuxt/ui".`

Let's see how to solve all the different troubles that can happen.

## Trouble Shooting

### Building the UI package
 
As the log above explains, our blog app is not able to resolve the UI package. So we will need to build it before we generate the blog.

On our `apps/blog/package.json` we can add a new script called `generate:ci`

```json
//package.json
  "scripts": {
    "generate:ci": "pnpm run -w build:ui && nuxt generate",
  }
```

Notice the use of `pnpm run` with a `-w` flag, this allows to run the command from the **root**.

Now let's add that command to our root package.json like this:

```json
"scripts": {
    "build:ui": "pnpm --dir packages/ui build",
  }
```

Last step will be update the command on the `netlify.toml` to this new `generate:ci`

```toml
[build]
  base = "apps/blog/"
  publish = ".output/public"
  command = "npx pnpm i --store=node_modules/.pnpm-store --frozen-lockfile && npx pnpm run generate:ci"
```

Annnnnnnnnnnnnnnd.

![Build failed again](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3mo1nkcqdyme5ep88np.png)
 
`#"@%·"` ok Alvaro, don't fall into eternal down-spiral of darkness... just breath.

### Getting the Publish directory right

![Build failed because of publish directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shkxpf1uv5rmmu4ri13e.png)

Gotta admit this part was a little frustrating for me. Following what Nuxt prompt when using `nuxt generate` the static files to deploy would be inside `.output/public` right?.

At this point, I was not able to find any real-life example of a Nuxt3 SSG deployed between a pnpm monorepo. All the references on internet were pretty simple, a Nuxt3 app on a simple repo, just push a Nitro preset will do the magic for you, all worked fine.

Why it was failing in this specific case? I search [Netlify Monorepo Docs](https://docs.netlify.com/configure-builds/monorepos/), Nuxt3 docs about [deployment](https://v3.nuxtjs.org/guide/deploy/presets) and I couldn't find any clue.

I tried every combination of things you can imagine, went to the multiverse and back, changing the base directory, the commands, everything.

![Doctor Strange checking 14 million on possibilities](https://media.giphy.com/media/WPtzThAErhBG5oXLeS/giphy.gif)

So I did the unthinkable. I gave up by tweeting my frustration about the integration:

{% embed https://twitter.com/alvarosabu/status/1530205877533347840 %}

Then a Hero landed heroically into the thread:

![Daniel Roe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ae4sp56souoqvzuatur0.png)

[Daniel Roe](https://github.com/danielroe) is well-known architect that works on the Nuxt Framework and an OSS saviour.

So we jumped on Discord, I created the reproduction repo and send him the logs and explained the context. (Here I want to make a special mention of the fact that Dani was on holiday, and his **dedication to helping** is so big, that he took the time to help 1 user, me, to solve my issue, I was amazed 😍)

After a few messages: 

![Daniel Croe Solution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2tjxco9pz2wkp5j9frc.png)
 

```
Issue is your dist symlink. Just remove it.
It was set to an absolute path rather than a relative one
```

🤔 wait what.... so maybe if I change the publish directory for `./dist` it would work? Worth trying.

![Netlify Build success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vdwzrm574r74gmzn67f.png)
 
😱 IT WORKED!!!! IT FINALLY WORKED!!!!

But why? Why does the `publish` needs to be `./dist` and not `.output/public`

The answer is right here in the code of [Nitro](https://github.com/unjs/nitropack/blob/43b1d023117060a216c290888c71297c3b3376f3/src/presets/netlify.ts#L7-L12) is the server-engine of Nuxt.

It's because the Netlify Preset sets it to `dist` by default rather than `.output` (Because that's the netlify default location). 

That's intended to support zero-config. So I asked if it makes sense to add zero-config preset for deploying between a monorepo. There is already an issue opened that could make it possible.

{% embed https://github.com/nuxt/framework/issues/158 %}

## Final Form

Until a zero-config preset for monorepos is out there, you can use this final form of the `netlify.toml` to make it work 😜.

![This is not even my final form](https://media.giphy.com/media/J1QcNGubdJPESH1bPo/giphy.gif)

```toml
[build.environment]
  NODE_VERSION = "16"
  NPM_FLAGS = "--version" # prevent Netlify npm install
[build]
  base = "apps/blog/"
  publish = "./dist"
  command = "npx pnpm i --store=node_modules/.pnpm-store --frozen-lockfile && npx pnpm run generate:ci"
[[redirects]]
  from = "/*"
  to = "index.html"
  status = 200
```

## Wrap up

This long experience helped me to understand a lot of things, like how **pnpm monorepos work**, how Nuxt is deployed on Netlify and most important:

The immense human quality and dedication of the contributors of OSS (In this case [Daniel Roe](https://github.com/sponsors/danielroe?sc=t) from [Nuxt](https://v3.nuxtjs.org/guide/deploy/presets) and [Zoltan Kochan](https://twitter.com/ZoltanKochan) of [pnpmjs](https://pnpm.io/)) 

Please consider Sponsor their work in the links above.

You can find the code for this repo here https://github.com/alvarosabu/pnpm-monorepo-nuxt

It's all for today folks. Happy coding!!!