-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: monorepo handling #434
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
affd94a
feat: monorepo handling
ascorbic db57006
chore: move demo to use monorepo features
ascorbic 93349f1
feat: add Nx config checks
ascorbic f3e671d
Merge branch 'main' into monorepo-weirdness
ascorbic f44e242
chore: fix tests
ascorbic 08d1bfe
Remove log
ascorbic 71b5c65
chore: changes from review
ascorbic 0b3fff6
fix: handle next imports
ascorbic bdd9bee
fix: sort out require handling
ascorbic 74518c8
chore: add todo for documenting monorepo in error message
ascorbic d34cccd
Merge branch 'main' into monorepo-weirdness
ascorbic 42a710d
Merge branch 'main' into monorepo-weirdness
ascorbic b5c992a
chore: add docs
ascorbic 759cd09
Merge branch 'main' into monorepo-weirdness
ascorbic ffdf020
chore: add links to error messages
ascorbic 09ee497
chore: set targetPort in nx doc
ascorbic de068e0
Merge branch 'main' into monorepo-weirdness
ascorbic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
## Using in a monorepo or subdirectory | ||
|
||
The Essential Next.js plugin works in most monorepos, but may need some configuration changes. This depends on the type of monorepo and the tooling that you use. | ||
|
||
### Self-contained subdirectory | ||
|
||
If your Next.js site is in a subdirectory of the repo, but doesn't rely on installing or compiling anything outside of that directory, then the simplest arrangement is to set the `base` of the site to that directory. This can be done either in the Netlify dashboard or in the `netlify.toml`. If your site is in `/frontend`, you should set up your site with the following in the root of the repo: | ||
|
||
```toml | ||
# ./netlify.toml | ||
[build] | ||
base="frontend" | ||
``` | ||
You can then place another `netlify.toml` file in `/frontend/` that configures the actual build: | ||
|
||
```toml | ||
# ./frontend/netlify.toml | ||
|
||
[build] | ||
command = "npm run build" | ||
publish = "out" | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-nextjs" | ||
``` | ||
|
||
### Yarn workspace | ||
|
||
If your site is a yarn workspace - including one that uses lerna - you should keep the base as the root of the repo, but change the configuration as follows. Assuming the site is in `/packages/frontend/`: | ||
|
||
```toml | ||
# ./netlify.toml | ||
|
||
[build] | ||
command = "next build packages/frontend" | ||
publish = "packages/frontend/out" | ||
|
||
[dev] | ||
command = "next dev packages/frontend" | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-nextjs" | ||
``` | ||
|
||
Ensure that the `next.config.js` is in the site directory, i.e. `/packages/frontend/next.config.js`. You must ensure that there is either a `yarn.lock` in the root of the site, or the environment variable `NETLIFY_USE_YARN` is set to true. | ||
|
||
### Lerna monorepo using npm | ||
|
||
If your monorepo uses Yarn workspaces, then set it up as shown above in the Yarn workspace section. If it uses npm then it is a little more complicated. First, you need to ensure that the `next` package is installed as a top-level dependency, i.e. it is in `./package.json` rather than `packages/frontend/package.json`. This is because it needs to be installed before lerna is bootstrapped as the build plugin needs to use it. Generally, hoisting as many packages to the top level as possible is best, so that they are more efficiently cached. You then should change the build command, and make it similar to this: | ||
|
||
```toml | ||
# ./netlify.toml | ||
|
||
[build] | ||
command = "lerna bootstrap && next build packages/frontend" | ||
publish = "packages/frontend/out" | ||
|
||
[dev] | ||
command = "next dev packages/frontend" | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-nextjs" | ||
``` | ||
|
||
### Nx | ||
|
||
[Nx](https://nx.dev/) is a build framework that handles scaffolding, building and deploying projects. It has support for Next.js via the `@nrwl/next` package. When building a Next.js site, it changes a lot of the configuraiton on the fly, and has quite a different directory structure to a normal Next.js site. The Essential Next.js plugin has full support for sites that use Nx, but there are a few required changes that you must make to the configuration. | ||
|
||
First, you need to make the `publish` directory point at a dirctory called `out` inside the app directory, rather than the build directory. If your app is called `myapp`, your `netlify.toml` should look something like: | ||
|
||
```toml | ||
# ./netlify.toml | ||
|
||
[build] | ||
command = "npm run build" | ||
publish = "apps/myapp/out" | ||
|
||
[dev] | ||
command = "npm run start" | ||
targetPort = 4200 | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-nextjs" | ||
``` | ||
|
||
You also need to make a change to the `next.config.js` inside the app directory. By default, Nx changes the Next.js `distDir` on the fly, changing it to a directory in the root of the repo. The Essential Next.js plugin can't read this value, so has no way of determining where the build files can be found. However, if you change the `distDir` in the config to anything except `.next`, then `Nx` will leave it unchanged, and the Essential Next.js plugin can read the value from there. e.g. | ||
|
||
```js | ||
// ./apps/myapp/next.config.js | ||
|
||
const withNx = require('@nrwl/next/plugins/with-nx'); | ||
|
||
module.exports = withNx({ | ||
distDir: '.dist', | ||
target: 'serverless' | ||
}); | ||
|
||
``` | ||
|
||
### Other monorepos | ||
|
||
Other arrangements may work: for more details, see [the monorepo documentation](https://docs.netlify.com/configure-builds/common-configurations/monorepos/). The important points are: | ||
|
||
1. The `next` package must be installed as part of the initial `npm install` or `yarn install`, not from the build command. | ||
2. The `publish` directory must be called `out`, and should be in the same directory as the `next.config.js` file. e.g. | ||
|
||
``` | ||
backend/ | ||
frontend/ | ||
|- next.config.js | ||
|- out | ||
netlify.toml | ||
package.json | ||
``` | ||
If you have another monorepo tool that you are using, we would welcome PRs to add instructions to this document. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const { existsSync } = require('fs') | ||
const { EOL } = require('os') | ||
const path = require('path') | ||
|
||
const checkNxConfig = ({ netlifyConfig, nextConfig, failBuild, constants: { PUBLISH_DIR } }) => { | ||
const errors = [] | ||
if (nextConfig.distDir === '.next') { | ||
errors.push( | ||
"- When using Nx you must set a value for 'distDir' in your next.config.js, and the value cannot be '.next'", | ||
) | ||
} | ||
// The PUBLISH_DIR constant is normalized, so no leading slash is needed | ||
if (!PUBLISH_DIR.startsWith('apps/')) { | ||
errors.push( | ||
"Please set the 'publish' value in your Netlify build config to a folder inside your app directory. e.g. 'apps/myapp/out'", | ||
) | ||
} | ||
// Look for the config file as a sibling of the publish dir | ||
const expectedConfigFile = path.resolve(netlifyConfig.build.publish, '..', 'next.config.js') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🥴 |
||
|
||
if (expectedConfigFile !== nextConfig.configFile) { | ||
const confName = path.relative(process.cwd(), nextConfig.configFile) | ||
errors.push( | ||
`- Using incorrect config file '${confName}'. Expected to use '${path.relative( | ||
process.cwd(), | ||
expectedConfigFile, | ||
)}'`, | ||
) | ||
|
||
if (existsSync(expectedConfigFile)) { | ||
errors.push( | ||
`Please move or delete '${confName}'${confName === 'next.config.js' ? ' from the root of your site' : ''}.`, | ||
) | ||
} else { | ||
errors.push( | ||
`Please move or delete '${confName}'${ | ||
confName === 'next.config.js' ? ' from the root of your site' : '' | ||
}, and create '${path.relative(process.cwd(), expectedConfigFile)}' instead.`, | ||
) | ||
} | ||
} | ||
|
||
if (errors.length !== 0) { | ||
failBuild( | ||
// TODO: Add ntl.fyi link to docs | ||
[ | ||
'Invalid configuration', | ||
...errors, | ||
'See the docs on using Nx with Netlify for more information: https://ntl.fyi/nx-next', | ||
].join(EOL), | ||
) | ||
} | ||
} | ||
|
||
module.exports = checkNxConfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const { existsSync } = require('fs') | ||
const path = require('path') | ||
|
||
/** | ||
* If we're in a monorepo then the Next root may not be the same as the base directory | ||
* If there's no next.config.js in the root, we instead look for it as a sibling of the publish dir | ||
*/ | ||
const getNextRoot = ({ netlifyConfig }) => { | ||
let nextRoot = process.cwd() | ||
if (!existsSync(path.join(nextRoot, 'next.config.js')) && netlifyConfig.build.publish) { | ||
nextRoot = path.dirname(netlifyConfig.build.publish) | ||
} | ||
return nextRoot | ||
} | ||
ascorbic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
module.exports = getNextRoot |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* We can't require() these normally, because the "next" package might not be resolvable from the root of a monorepo | ||
*/ | ||
const resolveNextModule = (module, nextRoot) => { | ||
// Get the default list of require paths... | ||
const paths = require.resolve.paths(module) | ||
// ...add the root of the Next site to the beginning of that list so we try it first... | ||
paths.unshift(nextRoot) | ||
// ...then resolve the module using that list of paths. | ||
return require.resolve(module, { paths }) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh my |
||
|
||
module.exports = resolveNextModule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥 this is so great!!!!!