feat(node): nx support (#606)

* feat(example): add node-nx-next workspace scaffold

- Initialize an Nx workspace with a Next.js application using pnpm.
- Configure workspace tooling including TypeScript, Prettier, and ESLint.
- Include a generation script to automate the workspace setup process.

Generated-by: aiautocommit

* feat(node): add BuildScriptContains helper to PackageJson

Generated-by: aiautocommit

* feat(node): add support for Nx workspaces with Next.js

- Implement Nx workspace detection and package resolution.
- Enable automatic Nx build commands when no root build script is detected.
- Add RAILPACK_NX_APP environment variable to support selecting specific apps in multi-app workspaces.
- Refactor Next.js detection logic to better support monorepo and Nx-inferred architectures.
- Include unit tests for Nx workspace detection and build/start command generation.

Generated-by: aiautocommit

* docs: add documentation for Nx monorepo support

- Explain how Railpack handles Nx workspaces, including automatic detection and support for inferred tasks.
- Document the new `RAILPACK_NX_APP` configuration variable to target specific apps in multi-app workspaces.
- Detail the build and start behavior for Next.js applications within an Nx context.

Generated-by: aiautocommit

* test: add snapshot for Nx Node.js and Next.js example

Generated-by: aiautocommit

* chore: bump railpack images and set version to dev

- Update builder and runtime images to mise-2026.7.7
- Include RAILPACK_VERSION environment variable in build plan

Generated-by: aiautocommit
This commit is contained in:
Michael Bianco
2026-07-21 12:50:43 -06:00
committed by GitHub
parent 7565917024
commit 14a60ffdf8
37 changed files with 13317 additions and 14 deletions
@@ -0,0 +1,207 @@
{
"caches": {
"apt": {
"directory": "/var/cache/apt",
"type": "locked"
},
"apt-lists": {
"directory": "/var/lib/apt/lists",
"type": "locked"
},
"next-apps-web": {
"directory": "/app/apps/web/.next/cache",
"type": "shared"
},
"node-modules": {
"directory": "/app/node_modules/.cache",
"type": "shared"
},
"pnpm-install": {
"directory": "/opt/pnpm/store",
"type": "shared"
}
},
"deploy": {
"base": {
"step": "packages:apt:runtime"
},
"inputs": [
{
"include": [
"/mise/shims",
"/mise/installs",
"/usr/local/bin/mise",
"/etc/mise/config.toml",
"/root/.local/state/mise"
],
"step": "packages:mise"
},
{
"include": [
"/app/node_modules"
],
"step": "build"
},
{
"exclude": [
"node_modules",
".yarn"
],
"include": [
"/root/.cache",
"."
],
"step": "build"
}
],
"startCommand": "cd apps/web \u0026\u0026 next start",
"variables": {
"CI": "true",
"NODE_ENV": "production",
"NPM_CONFIG_FETCH_RETRIES": "5",
"NPM_CONFIG_FUND": "false",
"NPM_CONFIG_PRODUCTION": "false",
"NPM_CONFIG_UPDATE_NOTIFIER": "false",
"RAILPACK_VERSION": "dev"
}
},
"steps": [
{
"assets": {
"generated-mise-toml": "[generated-mise-toml]"
},
"commands": [
{
"path": "/mise/shims"
},
{
"customName": "create mise config",
"name": "generated-mise-toml",
"path": "/etc/mise/config.toml"
},
{
"cmd": "mise install",
"customName": "install mise packages: node, pnpm"
}
],
"inputs": [
{
"image": "ghcr.io/railwayapp/railpack-builder:mise-2026.7.7"
}
],
"name": "packages:mise",
"variables": {
"MISE_CACHE_DIR": "/mise/cache",
"MISE_CONFIG_DIR": "/mise",
"MISE_DATA_DIR": "/mise",
"MISE_INSTALLS_DIR": "/mise/installs",
"MISE_SHIMS_DIR": "/mise/shims"
}
},
{
"caches": [
"pnpm-install"
],
"commands": [
{
"path": "/app/node_modules/.bin"
},
{
"cmd": "mkdir -p /app/node_modules/.cache"
},
{
"dest": ".npmrc",
"src": ".npmrc"
},
{
"dest": "apps/web/package.json",
"src": "apps/web/package.json"
},
{
"dest": "package.json",
"src": "package.json"
},
{
"dest": "pnpm-lock.yaml",
"src": "pnpm-lock.yaml"
},
{
"dest": "pnpm-workspace.yaml",
"src": "pnpm-workspace.yaml"
},
{
"path": "/opt/pnpm"
},
{
"cmd": "pnpm add -g node-gyp"
},
{
"cmd": "pnpm install --frozen-lockfile --prefer-offline"
}
],
"inputs": [
{
"step": "packages:mise"
}
],
"name": "install",
"variables": {
"CI": "true",
"NODE_ENV": "production",
"NPM_CONFIG_FETCH_RETRIES": "5",
"NPM_CONFIG_FUND": "false",
"NPM_CONFIG_PRODUCTION": "false",
"NPM_CONFIG_UPDATE_NOTIFIER": "false",
"PNPM_HOME": "/opt/pnpm",
"PNPM_STORE_DIR": "/opt/pnpm/store"
}
},
{
"caches": [
"node-modules",
"next-apps-web"
],
"commands": [
{
"cmd": "nx build @node-nx-next/web"
}
],
"inputs": [
{
"step": "install"
},
{
"include": [
"."
],
"local": true
}
],
"name": "build",
"secrets": [
"*"
],
"variables": {
"NEXT_TELEMETRY_DISABLED": "1"
}
},
{
"caches": [
"apt",
"apt-lists"
],
"commands": [
{
"cmd": "sh -c 'apt-get update \u0026\u0026 apt-get install -y libatomic1'",
"customName": "install apt packages: libatomic1"
}
],
"inputs": [
{
"image": "ghcr.io/railwayapp/railpack-runtime:mise-2026.7.7"
}
],
"name": "packages:apt:runtime"
}
]
}
+23 -6
View File
@@ -177,8 +177,10 @@ func (p *NodeProvider) StartCommandHelp() string {
"2. A \"main\" field in your package.json pointing to your entry file:\n" +
" \"main\": \"src/server.js\"\n\n" +
"3. An index.js or index.ts file in your project root\n\n" +
"4. An Nx workspace with a Next.js app (nx.json + next)\n\n" +
"If you have a static site, you can set the RAILPACK_SPA_OUTPUT_DIR environment variable\n" +
"containing the directory of your built static files."
"containing the directory of your built static files.\n\n" +
"For multi-app Nx workspaces, set RAILPACK_NX_APP to select which app to deploy."
}
func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
@@ -191,6 +193,11 @@ func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
} else if p.isNuxt() {
// Default Nuxt start command
return "node .output/server/index.mjs"
} else if pkg, _, ok := p.resolveNxDeployPackage(ctx); ok {
// Prefer next start from the app dir so prune does not require the nx CLI at runtime
if p.isNextAppPackage(pkg, ctx) {
return nxNextStartCommand(pkg)
}
}
return ""
@@ -199,15 +206,23 @@ func (p *NodeProvider) GetStartCommand(ctx *generate.GenerateContext) string {
func (p *NodeProvider) Build(ctx *generate.GenerateContext, build *generate.CommandStepBuilder) {
build.AddInput(ctx.NewLocalLayer())
_, ok := p.packageJson.Scripts["build"]
if ok {
if p.packageJson.HasScript("build") {
build.AddCommands([]plan.Command{
plan.NewExecCommand(p.packageManager.RunCmd("build")),
})
if p.isNext() {
// TODO I don't love how we are adding runtime config vars in `build`
build.AddVariables(map[string]string{"NEXT_TELEMETRY_DISABLED": "1"})
}
} else if _, projectName, ok := p.resolveNxDeployPackage(ctx); ok {
// TODO this `resolveNxDeployPackage` logic feels messy, we should refactor this a bit
// Nx infers targets from plugins; root package.json often has no build script
build.AddCommands([]plan.Command{
plan.NewExecCommand(nxBuildCommand(projectName)),
})
build.AddVariables(map[string]string{"NEXT_TELEMETRY_DISABLED": "1"})
}
p.addCachesToBuildStep(ctx, build)
@@ -237,10 +252,12 @@ func (p *NodeProvider) addCachesToBuildStep(ctx *generate.GenerateContext, build
build.AddCache(ctx.Caches.AddCache("node-modules", NODE_MODULES_CACHE))
p.addFrameworkCaches(ctx, build, "next", func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
if pkg.PackageJson.HasScript("build") {
return strings.Contains(pkg.PackageJson.Scripts["build"], "next build")
// Script-based Next apps (including monorepo packages with explicit next build)
if pkg.PackageJson.BuildScriptContains("next build") {
return true
}
return false
// Nx and similar toolchains infer targets; detect via next dep / next.config
return p.isNextAppPackage(pkg, ctx)
}, ".next/cache")
p.addFrameworkCaches(ctx, build, "remix", func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
+8 -4
View File
@@ -2,7 +2,6 @@ package node
import (
"fmt"
"strings"
"testing"
"github.com/railwayapp/railpack/core/app"
@@ -175,6 +174,11 @@ func TestGetNextApps(t *testing.T) {
path: "../../../examples/node-turborepo",
want: []string{"apps/web"},
},
{
name: "nx next workspace",
path: "../../../examples/node-nx-next",
want: []string{"apps/web"},
},
}
for _, tt := range tests {
@@ -185,10 +189,10 @@ func TestGetNextApps(t *testing.T) {
require.NoError(t, err)
nextPackages, err := provider.getPackagesWithFramework(ctx, func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
if pkg.PackageJson.HasScript("build") {
return strings.Contains(pkg.PackageJson.Scripts["build"], "next build")
if pkg.PackageJson.BuildScriptContains("next build") {
return true
}
return false
return provider.isNextAppPackage(pkg, ctx)
})
require.NoError(t, err)
+170
View File
@@ -0,0 +1,170 @@
package node
import (
"fmt"
"path/filepath"
"strings"
"github.com/railwayapp/railpack/core/generate"
)
// isNx reports whether this is an Nx workspace we can drive with nx CLI fallbacks.
func (p *NodeProvider) isNx(ctx *generate.GenerateContext) bool {
if !ctx.App.HasFile("nx.json") {
return false
}
if p.workspace != nil && p.workspace.HasDependency("nx") {
return true
}
return false
}
// isNextAppPackage identifies a deployable Next.js package in a monorepo.
// Root packages that only list next as a shared dependency (common in Nx scaffolds)
// are excluded unless they have a next.config or a next build script.
func (p *NodeProvider) isNextAppPackage(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
if pkg == nil || pkg.PackageJson == nil {
return false
}
if p.packageHasNextConfig(ctx, pkg.Path) {
return true
}
if !pkg.PackageJson.hasDependency("next") {
return false
}
if pkg.PackageJson.BuildScriptContains("next build") {
return true
}
// Workspace packages with next but no scripts (Nx inferred targets)
return pkg.Path != ""
}
func (p *NodeProvider) packageHasNextConfig(ctx *generate.GenerateContext, packagePath string) bool {
for _, name := range nextConfigFiles {
path := name
if packagePath != "" {
path = filepath.Join(packagePath, name)
}
if ctx.App.HasFile(path) {
return true
}
}
return false
}
func (p *NodeProvider) getNxNextPackages(ctx *generate.GenerateContext) []*WorkspacePackage {
packages := []*WorkspacePackage{}
if p.workspace == nil {
return packages
}
if p.isNextAppPackage(p.workspace.Root, ctx) {
packages = append(packages, p.workspace.Root)
}
for _, pkg := range p.workspace.Packages {
if p.isNextAppPackage(pkg, ctx) {
packages = append(packages, pkg)
}
}
return packages
}
// nxProjectName returns the name Nx uses for a package (package.json name, or directory basename).
func nxProjectName(pkg *WorkspacePackage) string {
if pkg.PackageJson != nil && pkg.PackageJson.Name != "" {
return pkg.PackageJson.Name
}
if pkg.Path != "" {
return filepath.Base(pkg.Path)
}
return ""
}
func matchesNxAppSelector(pkg *WorkspacePackage, selector string) bool {
if selector == "" {
return false
}
if pkg.PackageJson != nil && pkg.PackageJson.Name == selector {
return true
}
if pkg.Path == selector {
return true
}
if pkg.Path != "" && filepath.Base(pkg.Path) == selector {
return true
}
// "@scope/web" matches selector "web"
if pkg.PackageJson != nil {
name := pkg.PackageJson.Name
if i := strings.LastIndex(name, "/"); i >= 0 && name[i+1:] == selector {
return true
}
}
return false
}
// resolveNxDeployPackage picks the Nx app to build/start when root scripts are absent.
// Returns the workspace package, Nx project name, and whether a selection was made.
func (p *NodeProvider) resolveNxDeployPackage(ctx *generate.GenerateContext) (*WorkspacePackage, string, bool) {
if !p.isNx(ctx) {
return nil, "", false
}
packages := p.getNxNextPackages(ctx)
if len(packages) == 0 {
return nil, "", false
}
if selector, _ := ctx.Env.GetConfigVariable("NX_APP"); selector != "" {
for _, pkg := range packages {
if matchesNxAppSelector(pkg, selector) {
name := nxProjectName(pkg)
ctx.Logger.LogInfo("Using Nx app %s (RAILPACK_NX_APP=%s)", name, selector)
return pkg, name, true
}
}
ctx.Logger.LogWarn("RAILPACK_NX_APP=%s did not match any Next.js app package", selector)
return nil, "", false
}
if len(packages) == 1 {
pkg := packages[0]
name := nxProjectName(pkg)
ctx.Logger.LogInfo("Using Nx app %s", name)
return pkg, name, true
}
names := make([]string, 0, len(packages))
for _, pkg := range packages {
names = append(names, nxProjectName(pkg))
}
ctx.Logger.LogWarn("Multiple Next.js apps found in Nx workspace (%s). Set RAILPACK_NX_APP to choose one.", strings.Join(names, ", "))
return nil, "", false
}
// nxBuildCommand returns the shell command to build an Nx project.
func nxBuildCommand(projectName string) string {
return fmt.Sprintf("nx build %s", projectName)
}
// nxNextStartCommand returns a production start command for a Next.js app package.
func nxNextStartCommand(pkg *WorkspacePackage) string {
if pkg.Path == "" {
return DefaultNextStartCommand
}
return fmt.Sprintf("cd %s && %s", pkg.Path, DefaultNextStartCommand)
}
+142
View File
@@ -0,0 +1,142 @@
package node
import (
"testing"
"github.com/railwayapp/railpack/core/plan"
testingUtils "github.com/railwayapp/railpack/core/testing"
"github.com/stretchr/testify/require"
)
func TestIsNx(t *testing.T) {
tests := []struct {
name string
path string
want bool
}{
{
name: "nx next workspace",
path: "../../../examples/node-nx-next",
want: true,
},
{
name: "plain next app",
path: "../../../examples/node-next",
want: false,
},
{
name: "turborepo",
path: "../../../examples/node-turborepo",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, tt.path)
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
require.Equal(t, tt.want, provider.isNx(ctx))
})
}
}
func TestResolveNxDeployPackage(t *testing.T) {
t.Run("single next app in nx workspace", func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
require.True(t, ok)
require.Equal(t, "apps/web", pkg.Path)
require.Equal(t, "@node-nx-next/web", name)
})
t.Run("RAILPACK_NX_APP selects by short name", func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
ctx.Env.Variables["RAILPACK_NX_APP"] = "web"
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
require.True(t, ok)
require.Equal(t, "apps/web", pkg.Path)
require.Equal(t, "@node-nx-next/web", name)
})
t.Run("RAILPACK_NX_APP selects by package name", func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
ctx.Env.Variables["RAILPACK_NX_APP"] = "@node-nx-next/web"
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
pkg, name, ok := provider.resolveNxDeployPackage(ctx)
require.True(t, ok)
require.Equal(t, "apps/web", pkg.Path)
require.Equal(t, "@node-nx-next/web", name)
})
t.Run("non-nx project", func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-next")
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
_, _, ok := provider.resolveNxDeployPackage(ctx)
require.False(t, ok)
})
}
func TestNxBuildAndStartCommands(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-nx-next")
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
require.Equal(t, "cd apps/web && next start", provider.GetStartCommand(ctx))
// Build step should use nx when root has no build script
build := ctx.NewCommandStep("build")
provider.Build(ctx, build)
var foundNxBuild bool
for _, cmd := range build.Commands {
if execCmd, ok := cmd.(plan.ExecCommand); ok && execCmd.Cmd == "nx build @node-nx-next/web" {
foundNxBuild = true
break
}
}
require.True(t, foundNxBuild, "expected nx build command in build step")
require.Equal(t, "1", build.Variables["NEXT_TELEMETRY_DISABLED"])
}
func TestNxDoesNotOverrideRootScripts(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-turborepo")
provider := NodeProvider{}
err := provider.Initialize(ctx)
require.NoError(t, err)
// turborepo has root start/build scripts and is not Nx
require.False(t, provider.isNx(ctx))
require.Equal(t, "npm run start", provider.GetStartCommand(ctx))
}
func TestMatchesNxAppSelector(t *testing.T) {
pkg := &WorkspacePackage{
Path: "apps/web",
PackageJson: &PackageJson{
Name: "@node-nx-next/web",
},
}
require.True(t, matchesNxAppSelector(pkg, "@node-nx-next/web"))
require.True(t, matchesNxAppSelector(pkg, "apps/web"))
require.True(t, matchesNxAppSelector(pkg, "web"))
require.False(t, matchesNxAppSelector(pkg, "docs"))
require.False(t, matchesNxAppSelector(pkg, ""))
}
+4
View File
@@ -42,6 +42,10 @@ func (p *PackageJson) GetScript(name string) string {
return p.Scripts[name]
}
func (p *PackageJson) BuildScriptContains(value string) bool {
return strings.Contains(p.GetScript("build"), value)
}
func (p *PackageJson) hasDependency(dependency string) bool {
if p.Dependencies != nil {
if _, ok := p.Dependencies[dependency]; ok {
+3
View File
@@ -12,6 +12,7 @@ func TestPackageJson(t *testing.T) {
packageJson := NewPackageJson()
assert.Equal(t, packageJson.HasScript("start"), false)
assert.Equal(t, packageJson.GetScript("build"), "")
assert.False(t, packageJson.BuildScriptContains("next build"))
assert.Equal(t, packageJson.hasDependency("react"), false)
})
@@ -53,6 +54,8 @@ func TestPackageJson(t *testing.T) {
assert.True(t, packageJson.HasScript("start"))
assert.Equal(t, "next start", packageJson.GetScript("start"))
assert.Equal(t, "next build", packageJson.GetScript("build"))
assert.True(t, packageJson.BuildScriptContains("next build"))
assert.False(t, packageJson.BuildScriptContains("vite build"))
assert.Equal(t, "next dev", packageJson.GetScript("dev"))
assert.False(t, packageJson.HasScript("test"))
+23 -4
View File
@@ -95,6 +95,7 @@ Railpack determines the start command in the following order:
| `RAILPACK_NODE_PRUNE_CMD` | Custom command to prune dependencies | `npm prune --omit=dev --ignore-scripts` |
| `RAILPACK_NODE_INSTALL_PATTERNS` | Custom patterns to install dependencies | `prisma` |
| `RAILPACK_ANGULAR_PROJECT` | Name of the Angular project to build | `my-app` |
| `RAILPACK_NX_APP` | Nx app to build and start (project name, package name, or path) | `web` or `@org/web` |
### Package Managers
@@ -124,18 +125,20 @@ Railpack supports building native modules and automatically configures `node-gyp
### Monorepo Support
Railpack automatically supports monorepo (workspaces) configurations with all major
package managers. No special configuration is required.
package managers. No special configuration is required.
**Supported Approaches:**
- **npm, bun, yarn**: Uses the `workspaces` field in `package.json`
- **pnpm**: Uses `pnpm-workspace.yaml` configuration
- **Nx**: Detects `nx.json` and builds Next.js apps even when targets are
inferred (no root `build`/`start` scripts)
See the [examples
folder](https://github.com/railwayapp/railpack/tree/main/examples) in the
repository for workspace examples across different package managers (e.g.,
`node-pnpm-workspaces`, `node-npm-workspaces`, `node-yarn-workspaces`,
`node-bun-workspaces`).
`node-bun-workspaces`, `node-nx-next`).
When building a monorepo, Railpack will:
@@ -145,8 +148,24 @@ When building a monorepo, Railpack will:
- Cache workspace node_modules appropriately
If your monorepo requires building a specific workspace package, ensure
your build and start scripts are defined in the root `package.json` or use
a [config file](/architecture/user-config) to specify custom commands.
your build and start scripts are defined in the root `package.json`, set
`RAILPACK_NX_APP` for multi-app Nx workspaces, or use a
[config file](/architecture/user-config) to specify custom commands.
#### Nx
Stock Nx workspaces often rely on [inferred
tasks](https://nx.dev/docs/concepts/inferred-tasks) instead of `package.json`
scripts. When Railpack detects Nx and the root has no `build`/`start` scripts:
- **Build**: `nx build <project>` (uses the package name, e.g.
`@org/web`)
- **Start** (Next.js): `cd apps/web && next start` so runtime does not depend
on the `nx` CLI
With a single Next.js app, selection is automatic. With multiple apps, set
`RAILPACK_NX_APP` to the project name, package name, or package path (e.g.
`web`, `@org/web`, or `apps/web`).
### Install
+13
View File
@@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+49
View File
@@ -0,0 +1,49 @@
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# compiled output
dist
tmp
out-tsc
# dependencies
node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
.nx/cache
.nx/workspace-data
.nx/migrate-runs
.claude/worktrees
.claude/settings.local.json
# Next.js
.next
out
+2
View File
@@ -0,0 +1,2 @@
strict-peer-dependencies=false
auto-install-peers=true
+5
View File
@@ -0,0 +1,5 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
+3
View File
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
+101
View File
@@ -0,0 +1,101 @@
# NodeNxNext
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨.
[Learn more about this workspace setup and its capabilities](https://nx.dev/nx-api/next?utm_source=nx_project&amp;utm_medium=readme&amp;utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed!
## Run tasks
To run the dev server for your app, use:
```sh
npx nx dev web
```
To create a production bundle:
```sh
npx nx build web
```
To see all available targets to run for a project, run:
```sh
npx nx show project web
```
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
[More about running tasks in the docs &raquo;](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Add new projects
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
Use the plugin's generator to create new projects.
To generate a new application, use:
```sh
npx nx g @nx/next:app demo
```
To generate a new library, use:
```sh
npx nx g @nx/react:lib mylib
```
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
[Learn more about Nx plugins &raquo;](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry &raquo;](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Set up CI!
### Step 1
To connect to Nx Cloud, run the following command:
```sh
npx nx connect
```
Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as:
- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
### Step 2
Use the following command to configure a CI workflow for your workspace:
```sh
npx nx g ci-workflow
```
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Install Nx Console
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
[Install Nx Console &raquo;](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
## Useful links
Learn more:
- [Learn more about this workspace setup](https://nx.dev/nx-api/next?utm_source=nx_project&amp;utm_medium=readme&amp;utm_campaign=nx_projects)
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
And join the Nx community:
- [Discord](https://go.nx.dev/community)
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
+30
View File
@@ -0,0 +1,30 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "commonjs"
},
"sourceMaps": true,
"exclude": [
"jest.config.[ct]s",
".*\\.spec.tsx?$",
".*\\.test.tsx?$",
"./src/jest-setup.ts$",
"./**/jest-setup.ts$",
".*.js$",
".*.d.ts$"
]
}
@@ -0,0 +1,12 @@
import nextEslintPluginNext from '@next/eslint-plugin-next';
import nx from '@nx/eslint-plugin';
import baseConfig from '../../eslint.config.mjs';
export default [
{ plugins: { '@next/next': nextEslintPluginNext } },
...nx.configs['flat/react-typescript'],
...baseConfig,
{
ignores: ['.next/**/*', '**/out-tsc'],
},
];
+6
View File
@@ -0,0 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module '*.svg' {
const content: any;
export const ReactComponent: any;
export default content;
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,9 @@
//@ts-check
/** @type {import('next').NextConfig} */
const nextConfig = {
// Next.js options go here
// See: https://nextjs.org/docs/app/api-reference/config/next-config-js
};
module.exports = nextConfig;
@@ -0,0 +1,10 @@
{
"name": "@node-nx-next/web",
"version": "0.0.1",
"private": true,
"dependencies": {
"next": "~16.1.6",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,3 @@
export async function GET(request: Request) {
return new Response('Hello, from API!');
}
@@ -0,0 +1,501 @@
html {
-webkit-text-size-adjust: 100%;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
Segoe UI,
Roboto,
Helvetica Neue,
Arial,
Noto Sans,
sans-serif,
Apple Color Emoji,
Segoe UI Emoji,
Segoe UI Symbol,
Noto Color Emoji;
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
pre {
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
Liberation Mono,
Courier New,
monospace;
}
svg {
display: block;
vertical-align: middle;
shape-rendering: auto;
text-rendering: optimizeLegibility;
}
pre {
background-color: rgba(55, 65, 81, 1);
border-radius: 0.25rem;
color: rgba(229, 231, 235, 1);
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
Liberation Mono,
Courier New,
monospace;
overflow: scroll;
padding: 0.5rem 0.75rem;
}
.shadow {
box-shadow:
0 0 #0000,
0 0 #0000,
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.rounded {
border-radius: 1.5rem;
}
.wrapper {
width: 100%;
}
.container {
margin-left: auto;
margin-right: auto;
max-width: 768px;
padding-bottom: 3rem;
padding-left: 1rem;
padding-right: 1rem;
color: rgba(55, 65, 81, 1);
width: 100%;
}
#welcome {
margin-top: 2.5rem;
}
#welcome h1 {
font-size: 3rem;
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1;
}
#welcome span {
display: block;
font-size: 1.875rem;
font-weight: 300;
line-height: 2.25rem;
margin-bottom: 0.5rem;
}
#hero {
align-items: center;
background-color: hsla(214, 62%, 21%, 1);
border: none;
box-sizing: border-box;
color: rgba(55, 65, 81, 1);
display: grid;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#hero .text-container {
color: rgba(255, 255, 255, 1);
padding: 3rem 2rem;
}
#hero .text-container h2 {
font-size: 1.5rem;
line-height: 2rem;
position: relative;
}
#hero .text-container h2 svg {
color: hsla(162, 47%, 50%, 1);
height: 2rem;
left: -0.25rem;
position: absolute;
top: 0;
width: 2rem;
}
#hero .text-container h2 span {
margin-left: 2.5rem;
}
#hero .text-container a {
background-color: rgba(255, 255, 255, 1);
border-radius: 0.75rem;
color: rgba(55, 65, 81, 1);
display: inline-block;
margin-top: 1.5rem;
padding: 1rem 2rem;
text-decoration: inherit;
}
#hero .logo-container {
display: none;
justify-content: center;
padding-left: 2rem;
padding-right: 2rem;
}
#hero .logo-container svg {
color: rgba(255, 255, 255, 1);
width: 66.666667%;
}
#middle-content {
align-items: flex-start;
display: grid;
gap: 4rem;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#learning-materials {
padding: 2.5rem 2rem;
}
#learning-materials h2 {
font-weight: 500;
font-size: 1.25rem;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.list-item-link {
align-items: center;
border-radius: 0.75rem;
display: flex;
margin-top: 1rem;
padding: 1rem;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 100%;
}
.list-item-link svg:first-child {
margin-right: 1rem;
height: 1.5rem;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1.5rem;
}
.list-item-link > span {
flex-grow: 1;
font-weight: 400;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link > span > span {
color: rgba(107, 114, 128, 1);
display: block;
flex-grow: 1;
font-size: 0.75rem;
font-weight: 300;
line-height: 1rem;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link svg:last-child {
height: 1rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1rem;
}
.list-item-link:hover {
color: rgba(255, 255, 255, 1);
background-color: hsla(162, 47%, 50%, 1);
}
.list-item-link:hover > span {
}
.list-item-link:hover > span > span {
color: rgba(243, 244, 246, 1);
}
.list-item-link:hover svg:last-child {
transform: translateX(0.25rem);
}
#other-links {
}
.button-pill {
padding: 1.5rem 2rem;
transition-duration: 300ms;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
align-items: center;
display: flex;
}
.button-pill svg {
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
flex-shrink: 0;
width: 3rem;
}
.button-pill > span {
letter-spacing: -0.025em;
font-weight: 400;
font-size: 1.125rem;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.button-pill span span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
.button-pill:hover svg,
.button-pill:hover {
color: rgba(255, 255, 255, 1) !important;
}
#nx-console:hover {
background-color: rgba(0, 122, 204, 1);
}
#nx-console svg {
color: rgba(0, 122, 204, 1);
}
#nx-console-jetbrains {
margin-top: 2rem;
}
#nx-console-jetbrains:hover {
background-color: rgba(255, 49, 140, 1);
}
#nx-console-jetbrains svg {
color: rgba(255, 49, 140, 1);
}
#nx-repo:hover {
background-color: rgba(24, 23, 23, 1);
}
#nx-repo svg {
color: rgba(24, 23, 23, 1);
}
#nx-cloud {
margin-bottom: 2rem;
margin-top: 2rem;
padding: 2.5rem 2rem;
}
#nx-cloud > div {
align-items: center;
display: flex;
}
#nx-cloud > div svg {
border-radius: 0.375rem;
flex-shrink: 0;
width: 3rem;
}
#nx-cloud > div h2 {
font-size: 1.125rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#nx-cloud > div h2 span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
#nx-cloud p {
font-size: 1rem;
line-height: 1.5rem;
margin-top: 1rem;
}
#nx-cloud pre {
margin-top: 1rem;
}
#nx-cloud a {
color: rgba(107, 114, 128, 1);
display: block;
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 1.5rem;
text-align: right;
}
#nx-cloud a:hover {
text-decoration: underline;
}
#commands {
padding: 2.5rem 2rem;
margin-top: 3.5rem;
}
#commands h2 {
font-size: 1.25rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#commands p {
font-size: 1rem;
font-weight: 300;
line-height: 1.5rem;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}
details {
align-items: center;
display: flex;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
width: 100%;
}
details pre > span {
color: rgba(181, 181, 181, 1);
display: block;
}
summary {
border-radius: 0.5rem;
display: flex;
font-weight: 400;
padding: 0.5rem;
cursor: pointer;
transition-property:
background-color,
border-color,
color,
fill,
stroke,
opacity,
box-shadow,
transform,
filter,
backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
summary:hover {
background-color: rgba(243, 244, 246, 1);
}
summary svg {
height: 1.5rem;
margin-right: 1rem;
width: 1.5rem;
}
#love {
color: rgba(107, 114, 128, 1);
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 3.5rem;
opacity: 0.6;
text-align: center;
}
#love svg {
color: rgba(252, 165, 165, 1);
width: 1.25rem;
height: 1.25rem;
display: inline;
margin-top: -0.25rem;
}
@media screen and (min-width: 768px) {
#hero {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
#hero .logo-container {
display: flex;
}
#middle-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -0,0 +1,18 @@
import './global.css';
export const metadata = {
title: 'Welcome to ',
description: 'Generated by create-nx-workspace',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
@@ -0,0 +1,2 @@
.page {
}
@@ -0,0 +1,469 @@
import styles from './page.module.css';
export default function Index() {
/*
* Replace the elements below with your own.
*
* Note: The corresponding styles are in the ./index.css file.
*/
return (
<div className={styles.page}>
<div className="wrapper">
<div className="container">
<div id="welcome">
<h1>
<span> Hello there, </span>
Welcome @node-nx-next/web 👋
</h1>
</div>
<div id="hero" className="rounded">
<div className="text-container">
<h2>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
<span>You&apos;re up and running</span>
</h2>
<a href="#commands"> What&apos;s next? </a>
</div>
<div className="logo-container">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
</svg>
</div>
</div>
<div id="middle-content">
<div id="learning-materials" className="rounded shadow">
<h2>Learning materials</h2>
<a
href="https://nx.dev/getting-started/intro?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
<span>
Documentation
<span> Everything is in there </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nx.dev/blog/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
<span>
Blog
<span> Changelog, features & events </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://www.youtube.com/@NxDevtools/videos?utm_source=nx-project&sub_confirmation=1"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<title>YouTube</title>
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
<span>
YouTube channel
<span> Nx Show, talks & tutorials </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
/>
</svg>
<span>
Interactive tutorials
<span> Create an app, step-by-step </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nxplaybook.com/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 14l9-5-9-5-9 5 9 5z" />
<path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
/>
</svg>
<span>
Video courses
<span> Nx custom courses </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
</div>
<div id="other-links">
<a
id="nx-console"
className="button-pill rounded shadow"
href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Visual Studio Code</title>
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
</svg>
<span>
Install Nx Console for VSCode
<span>The official VSCode extension for Nx.</span>
</span>
</a>
<a
id="nx-console-jetbrains"
className="button-pill rounded shadow"
href="https://plugins.jetbrains.com/plugin/21060-nx-console"
target="_blank"
rel="noreferrer"
>
<svg
height="48"
width="48"
viewBox="20 20 60 60"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m22.5 22.5h60v60h-60z" />
<g fill="#fff">
<path d="m29.03 71.25h22.5v3.75h-22.5z" />
<path d="m28.09 38 1.67-1.58a1.88 1.88 0 0 0 1.47.87c.64 0 1.06-.44 1.06-1.31v-5.98h2.58v6a3.48 3.48 0 0 1 -.87 2.6 3.56 3.56 0 0 1 -2.57.95 3.84 3.84 0 0 1 -3.34-1.55z" />
<path d="m36 30h7.53v2.19h-5v1.44h4.49v2h-4.42v1.49h5v2.21h-7.6z" />
<path d="m47.23 32.29h-2.8v-2.29h8.21v2.27h-2.81v7.1h-2.6z" />
<path d="m29.13 43.08h4.42a3.53 3.53 0 0 1 2.55.83 2.09 2.09 0 0 1 .6 1.53 2.16 2.16 0 0 1 -1.44 2.09 2.27 2.27 0 0 1 1.86 2.29c0 1.61-1.31 2.59-3.55 2.59h-4.44zm5 2.89c0-.52-.42-.8-1.18-.8h-1.29v1.64h1.24c.79 0 1.25-.26 1.25-.81zm-.9 2.66h-1.57v1.73h1.62c.8 0 1.24-.31 1.24-.86 0-.5-.4-.87-1.27-.87z" />
<path d="m38 43.08h4.1a4.19 4.19 0 0 1 3 1 2.93 2.93 0 0 1 .9 2.19 3 3 0 0 1 -1.93 2.89l2.24 3.27h-3l-1.88-2.84h-.87v2.84h-2.56zm4 4.5c.87 0 1.39-.43 1.39-1.11 0-.75-.54-1.12-1.4-1.12h-1.44v2.26z" />
<path d="m49.59 43h2.5l4 9.44h-2.79l-.67-1.69h-3.63l-.67 1.69h-2.71zm2.27 5.73-1-2.65-1.06 2.65z" />
<path d="m56.46 43.05h2.6v9.37h-2.6z" />
<path d="m60.06 43.05h2.42l3.37 5v-5h2.57v9.37h-2.26l-3.53-5.14v5.14h-2.57z" />
<path d="m68.86 51 1.45-1.73a4.84 4.84 0 0 0 3 1.13c.71 0 1.08-.24 1.08-.65 0-.4-.31-.6-1.59-.91-2-.46-3.53-1-3.53-2.93 0-1.74 1.37-3 3.62-3a5.89 5.89 0 0 1 3.86 1.25l-1.26 1.84a4.63 4.63 0 0 0 -2.62-.92c-.63 0-.94.25-.94.6 0 .42.32.61 1.63.91 2.14.46 3.44 1.16 3.44 2.91 0 1.91-1.51 3-3.79 3a6.58 6.58 0 0 1 -4.35-1.5z" />
</g>
</svg>
<span>
Install Nx Console for JetBrains
<span>
Available for WebStorm, Intellij IDEA Ultimate and more!
</span>
</span>
</a>
<div id="nx-cloud" className="rounded shadow">
<div>
<svg
id="nx-cloud-logo"
role="img"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill="transparent"
viewBox="0 0 24 24"
>
<path
strokeWidth="2"
d="M23 3.75V6.5c-3.036 0-5.5 2.464-5.5 5.5s-2.464 5.5-5.5 5.5-5.5 2.464-5.5 5.5H3.75C2.232 23 1 21.768 1 20.25V3.75C1 2.232 2.232 1 3.75 1h16.5C21.768 1 23 2.232 23 3.75Z"
/>
<path
strokeWidth="2"
d="M23 6v14.1667C23 21.7307 21.7307 23 20.1667 23H6c0-3.128 2.53867-5.6667 5.6667-5.6667 3.128 0 5.6666-2.5386 5.6666-5.6666C17.3333 8.53867 19.872 6 23 6Z"
/>
</svg>
<h2>
Nx Cloud
<span>Enable faster CI & better DX</span>
</h2>
</div>
<p>
You can activate distributed tasks executions and caching by
running:
</p>
<pre>nx connect</pre>
<a
href="https://nx.app/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
{' '}
What is Nx Cloud?{' '}
</a>
</div>
<a
id="nx-repo"
className="button-pill rounded shadow"
href="https://github.com/nrwl/nx?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
<span>
Nx is open source
<span> Love Nx? Give us a star! </span>
</span>
</a>
</div>
</div>
<div id="commands" className="rounded shadow">
<h2>Next steps</h2>
<p>Here are some things you can do with Nx:</p>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Add UI library
</summary>
<pre>
<span># Generate UI lib</span>
nx g @nx/next:library ui
<span># Add a component</span>
nx g @nx/next:component ui/src/lib/button
</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
View project details
</summary>
<pre>nx show project @node-nx-next/web --web</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
View interactive project graph
</summary>
<pre>nx graph</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Run affected commands
</summary>
<pre>
<span># see what&apos;s been affected by changes</span>
nx affected:graph
<span># run tests for current changes</span>
nx affected:test
<span># run e2e tests for current changes</span>
nx affected:e2e
</pre>
</details>
</div>
<p id="love">
Carefully crafted with
<svg
fill="currentColor"
stroke="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
</p>
</div>
</div>
</div>
);
}
@@ -0,0 +1,48 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "preserve",
"noEmit": true,
"emitDeclarationOnly": false,
"esModuleInterop": true,
"resolveJsonModule": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
},
"outDir": "dist",
"rootDir": "src",
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js",
"src/**/*.jsx",
"../../apps/web/.next/types/**/*.ts",
"../../dist/apps/web/.next/types/**/*.ts",
"next-env.d.ts"
],
"exclude": [
"out-tsc",
"dist",
"node_modules",
"jest.config.ts",
"jest.config.cts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
".next",
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs"
]
}
+42
View File
@@ -0,0 +1,42 @@
import nx from '@nx/eslint-plugin';
export default [
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
{
ignores: ['**/dist', '**/out-tsc'],
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?[jt]s$'],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
},
{
files: [
'**/*.ts',
'**/*.tsx',
'**/*.cts',
'**/*.mts',
'**/*.js',
'**/*.jsx',
'**/*.cjs',
'**/*.mjs',
],
// Override or add rules here
rules: {},
},
];
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env zsh
root="${0:A:h}"
scratch="$root/tmp/node-nx-next"
rm -rf "$scratch"
mkdir -p "$scratch"
(
cd "$scratch"
pnpm dlx create-nx-workspace@latest . \
--preset=next \
--workspaces \
--appName=web \
--nextAppDir=true \
--pm=pnpm \
--nxCloud=skip \
--interactive=false \
--skipGit \
--e2eTestRunner=none \
--unitTestRunner=none
)
# create-nx-workspace requires an empty target; sync into the example dir.
rsync -a --delete --exclude=generate.sh --exclude=tmp/ "$scratch/" "$root/"
rm -rf "$scratch"
+54
View File
@@ -0,0 +1,54 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"analytics": false,
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.mjs"
],
"sharedGlobals": []
},
"plugins": [
{
"plugin": "@nx/js/typescript",
"options": {
"typecheck": {
"targetName": "typecheck"
},
"build": {
"targetName": "build",
"configName": "tsconfig.lib.json",
"buildDepsName": "build-deps",
"watchDepsName": "watch-deps"
}
}
},
{
"plugin": "@nx/next/plugin",
"options": {
"startTargetName": "start",
"buildTargetName": "build",
"devTargetName": "dev",
"serveStaticTargetName": "serve-static",
"buildDepsTargetName": "build-deps",
"watchDepsTargetName": "watch-deps"
}
},
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "lint"
}
}
],
"generators": {
"@nx/next": {
"application": {
"style": "css",
"linter": "eslint"
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@node-nx-next/source",
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"private": true,
"dependencies": {
"next": "~16.1.6",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@next/eslint-plugin-next": "^16.1.6",
"@nx/eslint": "23.0.0",
"@nx/eslint-plugin": "23.0.0",
"@nx/js": "23.0.0",
"@nx/next": "23.0.0",
"@nx/workspace": "23.0.0",
"@swc-node/register": "~1.11.1",
"@swc/cli": "~0.8.0",
"@swc/core": "~1.15.5",
"@swc/helpers": "~0.5.18",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"eslint": "^9.8.0",
"eslint-config-next": "^16.1.6",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.1",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-hooks": "5.0.0",
"nx": "23.0.0",
"prettier": "~3.6.2",
"tslib": "^2.3.0",
"typescript": "~5.9.2",
"typescript-eslint": "^8.40.0"
}
}
+11239
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
packages:
- "apps/*"
+17
View File
@@ -0,0 +1,17 @@
[
{
"httpCheck": {
"path": "/",
"expected": 200,
"internalPort": 3000
}
},
{
"httpCheck": {
"path": "/api/hello",
"expected": 200,
"internalPort": 3000,
"expectedOutput": "Hello, from API!"
}
}
]
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"composite": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"importHelpers": true,
"isolatedModules": true,
"lib": ["es2022"],
"module": "esnext",
"moduleResolution": "bundler",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"strict": true,
"target": "es2022",
"customConditions": ["@node-nx-next/source"]
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.base.json",
"compileOnSave": false,
"files": [],
"references": [
{
"path": "./apps/web"
}
]
}