During this journey from setting up our Next.js app within an Nx workspace, to configuring Tailwind, Markdown rendering, Storybook and Cypress we're now at the point where we should look at the deployment of our site. Let's learn how to deploy to some static environment as well as leverage the rich features when deploying to Vercel.
Deploying our site as a set of static files
In our specific context of developing our personal portfolio website or blog, we can totally rely on static site generators. Tools like Jekyll, Eleventy, Hugo and so on do a perfect job in taking a set of markdown files and transform them into static HTML files. These can be deployed to any web server that is able to statically serve files. Often these tools are very opinionated on how you should structure your site and where to place the markdown files. This can be a pro and cons, depending on your needs. While for a simple portfolio website you don't really need any kind of backend, as your site grows, you might find it useful to have access to simple cloud functions and persistent backend storage to create a more dynamic experience for your visitors.
Turns out that with Next.js you have many of these properties already built in. As we learned in our very first article of the series, Next.js allows you to dynamically decide whether to statically or dynamically render it from the server.
In our specific case, we have just used static rendering so far, which means we can just "export" our site with this simple command:
npx nx export site
This generates a set of static HTML, CSS and JS files at dist/apps/site/exported
. With a plain simple HTTP server, capable of serving static files, we can run the exported Next.js application.
cd dist/apps/site/exported
npx http-server .
In many scenarios, such a deployment is all you ever want. You can configure GitHub Pages (even with your custom domain) to directly deploy your website from your GitHub repo. Nowadays GitHub has even SSL built-in, and alternatively, you can use CloudFlare to enable it.
Understanding deployments on Vercel
If you want to have static deployments, but at the same time the freedom to easily expand beyond that, I'd highly recommend having a look at Vercel.
Vercel deploys from your Git repository and has different deployment types.
-
Production deployment - Production deployments are made each time you merge to your production branch (e.g.
main
) or when you use thevercel --prod
command. Read more here. -
Preview deployment - This happens every time you push a new commit to a branch or when you run the
vercel
command. Read more here. - Instant rollbacks are also deployments, which happen whenever you revert any changes to a production deployment
Let's get started by configuring our project first.
Setting up our project on Vercel
To set up our deployments on Vercel, first go to https://vercel.com/, create an account or log into your existing one. Since we already have our Next.js project setup with Nx, we choose "Create a New Project".
We can directly import the repository from GitHub:
Once that's done, you'll have to run through the configuration, giving the Vercel platform a couple of inputs on how to build your project. Creating a team is optional and really just makes sense if you plan to collaborate with others on the project. Here's what it would look like if we created one.
In the next step, you can configure the project, giving it a name to identify it in your Vercel dashboard later. You can leave the other settings untouched.
Before going ahead and hit the Deploy we need to configure the "Build and Output Settings" to use the underlying Nx build commands.
Building your Next.js app for Vercel
Similarly to exporting the Next.js app (as we've seen a couple of sections ago), we can build the Next.js site for Vercel by running the build command:
npx nx build site --prod
Once that build succeeds, you can see the output in the dist/apps/site
directory.
Note: If needed, you can also customize the output path in the project configuration of your application
workspace.json
Let's have a look at how to configure Vercel to pick up these commands.
Production deployment on Vercel
This would be all Vercel needs to deploy your app successfully. Hence, let's go ahead and configure the "Build command" and "Output directory" as shown in the following screenshot:
Once you hit deploy, Vercel takes over and looks at your current production branch (in our case main
), fetches the source code, executes the build command we have specified above, and deploys it into production.
Congrats, you should now have a successful deployment ๐.
Going to the dashboard we can see more information about the deployment:
And obviously, we can go to the deployment URL (https://blog-series-nextjs-nx-alpha.vercel.app/articles/dynamic-routing) and see the site live:
Preview deployments on Vercel
We just deployed to production. As we described earlier, Vercel has another cool feature that comes in really handy: Preview Deployments.
When you push to a branch that is not the production branch, a preview deployment will be created automatically. Whenever that PR is then merged to the main branch, a production deployment will be triggered.
Let's create a new branch to test these out:
git checkout -b vercel-preview-deployment-test
Let's create a new article in _articles/welcome-to-vercel.mdx
---
title: 'Welcome to Vercel'
excerpt: 'How to deploy your Nx based Next.js app to Vercel'
date: '2021-08-25T05:35:07.322Z'
author:
name: JJ Kasper
---
Hey!! You just deployed your first Nx based Next.js site to Vercel!!
If you want to quickly test this locally, run
npx nx serve site
and navigate to http://localhost:4200/articles/welcome-to-vercel. You should see the article be rendered properly. Let's deploy it ๐. Commit your changes and push the branch:
git push --set-upstream origin vercel-preview-deployment-test
Note: make sure to use the name of the branch you've chosen.
If you go to the /deployments
page on your Vercel project, in my case https://vercel.com/nx-blog-series/blog-series-nextjs-nx/deployments, you should already see a deployment running which is marked as "Preview":
In addition, if you create a PR on GitHub, you'll also automatically get the info posted into the PR itself:
There's one thing though. When you make new changes to the _articles/*.mdx
files, it might happen that the changes aren't actually being reflected on the deployed preview, even though the deployment ran and finished successfully.
Inspecting the deployment logs, you might see something like "[retrieved from cache]
".
This is the Nx computation caching in action. Let's learn more.
Nx and Computation caching
Nx has an internal computation cache which helps to optimize for speed. Basically, whenever you execute a command which you already ran previously, and granted you didn't change any relevant file that might alter the outcome of that specific task, Nx just replays it from the cache. Sounds complicated? Here's a more detailed video on how this works: https://egghead.io/lessons/javascript-speed-up-with-nx-computation-caching
But wait a minute! We did actually change something: we updated one of our files in _articles
. Let's dig a bit deeper into how Nx caching works and how we can influence which files get included in the cache.
Nx uses its internal dependency graph to understand which files to include when it computes the cache. If we look at our dependency graph by running npx nx dep-graph
we can see that the _articles
folder is not present there.
As a result, it misses it when calculating the cache, and hence doesn't invalidate the cache when we change one of our article MDX files.
Nx is flexible enough to allow us to fix this issue. Here are the options:
- Adding our
_articles
files to the globalimplicitDependencies
- Moving our articles as a project into the
libs/
folder and referencing it as an implicit dependency on oursite
Next.js application - Adding our existing
_articles
folder as a node to the dependency graph and referencing it as an implicit dependency on oursite
Next.js application
Let's explore them.
Global implicit dependencies
Global implicit dependencies cause the entire Nx workspace to rebuild / retest etc. Basically, on every change of one of these global dependencies, the entire cache gets invalidated and everything gets rebuilt. As you can imagine, this is not always ideal, but there are some use cases where we might want this to happen. Examples are:
- changes to the CI build configuration when we definitely would want to make sure the CI runs all the projects
- changes to our global lint rule configuration file
- ...
You can specify these global implicit dependencies in the nx.json
. Here's an example configuration:
// nx.json
{
"implicitDependencies": {
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
".eslintrc.json": "*"
},
...
}
You can read more about it and the possible configuration options on the Nx docs.
For our _articles
folder, we could add an entry here that looks like the following:
// nx.json
{
"implicitDependencies": {
...
"_articles/*.mdx": "*"
},
...
}
With this configuration, every change to any *.mdx
file within the _articles
folder would invalidate the Nx cache and cause a full re-computation. This definitely fixes our issue with the Vercel deployments and would totally work for our simple use case. But imagine in a more real-world setting, where you have other apps in this workspace that are not really using the _articles
folder at all. Those would also always be recomputed, which is a waste of computation power and ultimately a waste of your time.
Registering _articles as a node in the Nx Dependency Graph
The other options which we had were the following:
- Moving our articles as a project into the
libs/
folder and referencing it as an implicit dependency on oursite
Next.js application - Adding our existing
_articles
folder as a node to the dependency graph and referencing it as an implicit dependency on oursite
Next.js application
I'm skipping the 1st point, as I think it wouldn't be worth the effort of generating a library in the libs
folder and removing all of the config files, because clearly, we wouldn't have any use for the TypeScript and Jest configuration. Moreover, I want to have the _articles
at the very root where they are easily accessible when I create new ones.
We can however just manually configure our _articles
folder s.t. Nx recognizes it as a node in its dependency graph. We can do that by manually adding a configuration in the workspace.json
:
// workspace.json
{
"version": 2,
...
"projects": {
...
"site-articles": {
"root": "_articles",
"sourceRoot": "_articles",
"projectType": "application",
"targets": {}
}
}
}
As you can see it is an empty configuration. Also, we use projectType: "application"
although it doesn't really matter in this case.
Next, we also need to add a new entry in our nx.json
:
// nx.json
{
"implicitDependencies": {
...
},
...
"projects": {
...
"site-articles": {
"tags": []
}
}
}
If we now run our Nx Dependency graph visualization, using npx nx dep-graph
, we should see our "site-articles" node appear on the graph:
Finally, we need to make sure to establish a connection from our Next.js app "site" to "site-articles". Nx doesn't recognize this relationship automatically, which it does just for source imports.
We could extend the Nx dependency graph's capabilities via a custom Nx plugin, but that is the topic of another article series ๐ . Read more about it here: https://nx.dev/latest/angular/structure/project-graph-plugins
To create this connection, we can add the site-articles
node to the implicitDependencies
property of site
:
// nx.json
{
"implicitDependencies": {
...
},
...
"projects": {
...
"site": {
"tags": [],
"implicitDependencies": ["site-articles"]
},
"site-articles": {
"tags": []
}
}
}
Re-running our dependency graph visualization now shows the correct relationship:
Now, every time we change an article, only our Next.js site application would be rebuilt, which is exactly what we wanted.
Use Nx to build and deploy only when something changed
Besides the Nx computation cache we've just discussed, Nx has another feature that helps you scale. Now again, talking about scaling when creating our personal portfolio and blog site is not really your top priority. The main benefit of going through this blog series though is that you can apply these concepts nearly 1-1 to a real-world project with Nx and Next.js. Even though scaling is not your main concern right now, it definitely might be as your Nx workspace grows and hosts more than just your Next.js application.
The Nx feature I'm talking about is so-called "affected commands". They only trigger for projects which are affected by the changes we made. Based on the dependency graph of your project and your Git history, Nx is able to figure out on which projects a given command needs to be executed. Without going too much into the details here, feel free to check out these resources for more info:
To only run our Vercel build if something changed that might have affected it, we can leverage Vercel's ignored build step feature. That feature requires us to respond with either an exit code 1
if the build is required or 0
if it should be canceled.
This Nx guide describes it in more depth: https://nx.dev/latest/react/guides/deploy-nextjs-to-vercel#skipping-build-if-the-application-is-not-affected
To set up affected builds for Vercel, copy the script mentioned in the guide I just linked and place it into the tools/vercel-deploy/vercel-affected-deploy.sh
file.
We should also adjust the APP
variable to reflect our own app name: site
. Most probably we could also inject this via some Vercel environment variable we define for the application. This would make the script more reusable, but I'll leave that for you. So here is the entire script:
# tools/vercel-deploy/vercel-affected-deploy.sh
# Name of the app to check. Change this to your application name!
APP=site
# Determine version of Nx installed
NX_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@nrwl/workspace'])")
TS_VERSION=$(node -e "console.log(require('./package.json').devDependencies['typescript'])")
# Install @nrwl/workspace in order to run the affected command
npm install -D @nrwl/workspace@$NX_VERSION --prefer-offline
npm install -D typescript@$TS_VERSION --prefer-offline
# Run the affected command, comparing latest commit to the one before that
npx nx affected:apps --plain --base HEAD~1 --head HEAD | grep $APP -q
# Store result of the previous command (grep)
IS_AFFECTED=$?
if [ $IS_AFFECTED -eq 1 ]; then
echo "๐ - Build cancelled"
exit 0
elif [ $IS_AFFECTED -eq 0 ]; then
echo "โ
- Build can proceed"
exit 1
fi
Note that there's the line where we print out all the affected apps (because we need to deploy those) and filter it for the provided application name:
npx nx affected:apps --plain --base HEAD~1 --head HEAD | grep $APP -q
By default, Nx compares the current Git HEAD with the main production branch. Make sure you set it to the one you're using in the nx.json
.
// nx.json
{
"implicitDependencies": {
...
},
"affected": {
"defaultBase": "main"
},
...
"projects": {
...
},
...
}
Make sure it matches your main branch name. In my case it is main
.
The script above explicitly passes the base and head references explicitly, setting them to HEAD~1
and HEAD
accordingly. Basically just comparing the changes that have been made from the last commit.
Note, right now it is not possible to get a reference to the last successful deployment commit SHA on Vercel. If that was possible we could pass that to
--base
and thus have an even more efficient affected command evaluation because it would allow us to truly determine what changed since the last deployment.
Configuring Nx Affected deploys on Vercel
Finally, let's configure the script on Vercel. Go to the Settings > Git
and scroll to the section "Ignored Build Step". Add ./tools/vercel-deploy/vercel-affected-deploy.sh
and save your configuration.
Testing Nx affected deploys on Vercel
To verify whether our script works, let's create a new React application in our workspace. For the sake of this simple showcase, let's call it "react-demo".
npx nx g @nrwl/react:app
Also, let's create a new React library react-ui
:
npx nx g @nrwl/react:lib reactui
Finally, let's change the generated React application in a way to create dependencies to react-ui
as well as shared-ui
. To do so, open apps/react-app/src/app/app.tsx
and replace its content with the following:
import styles from './app.module.css';
import { ReactComponent as Logo } from './logo.svg';
import { TopicButton } from '@juridev/shared/ui';
import { Reactui } from '@juridev/reactui';
export function App() {
return (
<div className={styles.app}>
<header className="flex">
<Logo width="75" height="75" />
<h1>Welcome to react-app!</h1>
</header>
<main>
<TopicButton topicName="React" />
<Reactui />
</main>
</div>
);
}
export default App;
If you now visualize the dependency graph with nx dep-graph
you should see something like the following:
Commit everything and push it to your GitHub repo.
When the build is triggered on Vercel, you should now see that the vercel-affected-deploy.sh
has been used.
Since we didn't change anything related to our Next.js application, the build gets canceled just as we expect.
Let's try to change something in the react-ui
library which is supposed to be our React application specific UI library.
// libs/reactui/src/lib/reactui.tsx
import './reactui.module.css';
/* eslint-disable-next-line */
export interface ReactuiProps {}
export function Reactui(props: ReactuiProps) {
return (
<div>
<h1>Welcome to Reactui!</h1>
<p>Nx โค๏ธ Next</p>
</div>
);
}
export default Reactui;
Commit the change, then execute the command Vercel will execute to determine whether to deploy our "site" app or not. Instead of affected:apps
we can also use affected:dep-graph
to show what changed in our last commit:
npx nx affected:dep-graph --base HEAD~1 --head HEAD
If you push this commit to your GitHub repo, Vercel would again cancel the deployment as expected.
If however, we make a change in our shared-ui
library, which our React application as well as our Next.js based site
application depends on, then a deployment would be triggered.
Conclusion
This article was quite packed with knowledge. We learned
- How to export our Next.js based site as a set of static assets
- What type of deployments Vercel offers
- How to configure our project on Vercel
- How to build our Next.js based Nx application for Vercel
- How to configure our Nx project on Vercel
- What Nx computation cache is all about and how to configure implicit dependencies
- How Nx affected commands work and how we can configure them on Vercel
See also:
- https://nx.dev/latest/react/guides/deploy-nextjs-to-vercel
- https://nextjs.org/docs/deployment
- https://vercel.com/docs/git
- https://nextjs.org/learn/basics/deploying-nextjs-app
- https://egghead.io/lessons/javascript-speed-up-with-nx-computation-caching
- https://nx.dev/latest/react/core-extended/affected
- https://egghead.io/lessons/javascript-scale-ci-runs-with-nx-affected-commands
GitHub repository
All the sources for this article can be found in this GitHub repository's branch: https://github.com/juristr/blog-series-nextjs-nx/tree/09-deploy-to-vercel
Learn more
๐ง Nx Docs
๐ฉโ๐ป Nx GitHub
๐ฌ Nrwl Community Slack
๐น Nrwl Youtube Channel
๐ฅ Free Egghead course
๐ง Need help with Angular, React, Monorepos, Lerna or Nx? Talk to us ๐
Also, if you liked this, click the โค๏ธ and make sure to follow Juri and Nx on Twitter for more!