diff --git a/.gitignore b/.gitignore
index 0f1f6e42..8e3e69a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ node_modules
 /postgres-data
 
 /app/styles/tailwind.css
+
+/.idea
\ No newline at end of file
diff --git a/nx.json b/nx.json
new file mode 100644
index 00000000..655c9d1d
--- /dev/null
+++ b/nx.json
@@ -0,0 +1,31 @@
+{
+  "extends": "nx/presets/core.json",
+  "npmScope": "remix",
+  "tasksRunnerOptions": {
+    "default": {
+      "runner": "nx/tasks-runners/default",
+      "options": {
+        "cacheableOperations": [
+          "build-all",
+          "validate-all",
+          "build:css",
+          "build:remix",
+          "build:server",
+          "typecheck",
+          "test:run",
+          "lint",
+          "test:e2e:run"
+        ]
+      }
+    }
+  },
+  "cli": {
+    "defaultProjectName": "blues-stack-template"
+  },
+  "pluginsConfig": {
+    "@nrwl/js": {
+      "analyzeSourceFiles": false,
+      "analyzePackageJson": false
+    }
+  }
+}
diff --git a/package.json b/package.json
index e6d2a54e..3fc68f7e 100644
--- a/package.json
+++ b/package.json
@@ -5,11 +5,9 @@
   "license": "",
   "sideEffects": false,
   "scripts": {
-    "build": "run-s build:*",
     "build:css": "npm run generate:css -- --minify",
     "build:remix": "remix build",
     "build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build",
-    "dev": "run-p dev:*",
     "dev:server": "cross-env NODE_ENV=development node --inspect --require ./node_modules/dotenv/config --require ./mocks ./build/server.js",
     "dev:build": "cross-env NODE_ENV=development npm run build:server -- --watch",
     "dev:remix": "cross-env NODE_ENV=development remix watch",
@@ -22,11 +20,13 @@
     "start": "cross-env NODE_ENV=production node ./build/server.js",
     "start:mocks": "cross-env NODE_ENV=production node --require ./mocks --require dotenv/config ./build/server.js",
     "test": "vitest",
+    "test:run": "vitest --run",
     "test:e2e:dev": "start-server-and-test dev http://localhost:3000 \"cypress open\"",
-    "pretest:e2e:run": "npm run build",
     "test:e2e:run": "cross-env PORT=8811 start-server-and-test start:mocks http://localhost:8811 \"cypress run\"",
     "typecheck": "tsc -b && tsc -b cypress",
-    "validate": "run-p \"test -- --run\" lint typecheck test:e2e:run"
+    "build": "nx build-all --output-style=compact",
+    "dev": "nx build-all --output-style=compact && nx dev-all",
+    "validate": "nx validate-all --output-style=compact"
   },
   "prettier": {},
   "eslintIgnore": [
@@ -76,7 +76,7 @@
     "eslint-config-prettier": "^8.5.0",
     "happy-dom": "^2.55.0",
     "msw": "^0.39.2",
-    "npm-run-all": "^4.1.5",
+    "nx": "14.1.4",
     "postcss": "^8.4.12",
     "prettier": "^2.6.1",
     "prettier-plugin-tailwindcss": "^0.1.8",
@@ -95,5 +95,39 @@
   },
   "prisma": {
     "seed": "ts-node --require tsconfig-paths/register prisma/seed.ts"
+  },
+  "nx": {
+    "targets": {
+      "build:remix": {
+        "outputs": [
+          "/build/index.js",
+          "/public/build"
+        ]
+      },
+      "build:server": {
+        "outputs": [
+          "/build/server.js"
+        ]
+      },
+      "build:css": {
+        "outputs": [
+          "/app/styles/tailwind.css"
+        ]
+      },
+      "test:e2e:run": {
+        "outputs": [
+          "/cypress/screenshots",
+          "/cypress/videos"
+        ],
+        "dependsOn": [
+          "build-all"
+        ]
+      },
+      "test:e2e:dev": {
+        "dependsOn": [
+          "build-all"
+        ]
+      }
+    }
   }
 }
diff --git a/project.json b/project.json
new file mode 100644
index 00000000..aa650c9b
--- /dev/null
+++ b/project.json
@@ -0,0 +1,40 @@
+{
+  "name": "blues-stack-template",
+  "targets": {
+    "dev-all": {
+      "executor": "nx:run-commands",
+      "options": {
+        "commands": [
+          {
+            "command": "nx dev:server blues-stack-template",
+            "prefix": "[SERVER]",
+            "color": "blue"
+          },
+          {
+            "command": "nx dev:build blues-stack-template",
+            "prefix": "[BUILD-]",
+            "color": "green"
+          },
+          {
+            "command": "nx dev:remix blues-stack-template",
+            "prefix": "[REMIX-]",
+            "color": "yellow"
+          },
+          {
+            "command": "nx dev:css blues-stack-template",
+            "prefix": "[CSS---]",
+            "color": "cyan"
+          }
+        ]
+      }
+    },
+    "validate-all": {
+      "executor": "nx:noop",
+      "dependsOn": ["test:run", "lint", "typecheck", "test:e2e:run"]
+    },
+    "build-all": {
+      "executor": "nx:noop",
+      "dependsOn": ["build:css", "build:remix", "build:server"]
+    }
+  }
+}
diff --git a/remix.init/index.js b/remix.init/index.js
index 56f718f0..b78bd8fc 100644
--- a/remix.init/index.js
+++ b/remix.init/index.js
@@ -2,75 +2,58 @@ const crypto = require("crypto");
 const fs = require("fs/promises");
 const path = require("path");
 
-const toml = require("@iarna/toml");
-const sort = require("sort-package-json");
-
-function escapeRegExp(string) {
-  // $& means the whole matched string
-  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-}
-
 function getRandomString(length) {
   return crypto.randomBytes(length).toString("hex");
 }
 
 async function main({ rootDirectory }) {
-  const README_PATH = path.join(rootDirectory, "README.md");
-  const FLY_TOML_PATH = path.join(rootDirectory, "fly.toml");
-  const EXAMPLE_ENV_PATH = path.join(rootDirectory, ".env.example");
-  const ENV_PATH = path.join(rootDirectory, ".env");
-  const PACKAGE_JSON_PATH = path.join(rootDirectory, "package.json");
-
-  const REPLACER = "blues-stack-template";
-
-  const DIR_NAME = path.basename(rootDirectory);
-  const SUFFIX = getRandomString(2);
-
-  const APP_NAME = (DIR_NAME + "-" + SUFFIX)
+  const APP_NAME = path.basename(rootDirectory);
+  const APP_ID = (APP_NAME + "-" + getRandomString(2))
     // get rid of anything that's not allowed in an app name
     .replace(/[^a-zA-Z0-9-_]/g, "-");
 
-  const [prodContent, readme, env, packageJson] = await Promise.all([
-    fs.readFile(FLY_TOML_PATH, "utf-8"),
-    fs.readFile(README_PATH, "utf-8"),
-    fs.readFile(EXAMPLE_ENV_PATH, "utf-8"),
-    fs.readFile(PACKAGE_JSON_PATH, "utf-8"),
-  ]);
+  // copy files
+  const filesToCopy = [["remix.init/gitignore", ".gitignore"]];
+  for (const [from, to] of filesToCopy) {
+    await fs.copyFile(
+      path.join(rootDirectory, from),
+      path.join(rootDirectory, to)
+    );
+  }
 
+  // update env to have SESSION_SECRET
+  const EXAMPLE_ENV_PATH = path.join(rootDirectory, ".env.example");
+  const ENV_PATH = path.join(rootDirectory, ".env");
+  const env = await fs.readFile(EXAMPLE_ENV_PATH, "utf-8");
   const newEnv = env.replace(
     /^SESSION_SECRET=.*$/m,
     `SESSION_SECRET="${getRandomString(16)}"`
   );
-
-  const prodToml = toml.parse(prodContent);
-  prodToml.app = prodToml.app.replace(REPLACER, APP_NAME);
-
-  const newReadme = readme.replace(
-    new RegExp(escapeRegExp(REPLACER), "g"),
-    APP_NAME
-  );
-
-  const newPackageJson =
-    JSON.stringify(
-      sort({ ...JSON.parse(packageJson), name: APP_NAME }),
-      null,
-      2
-    ) + "\n";
-
-  await Promise.all([
-    fs.writeFile(FLY_TOML_PATH, toml.stringify(prodToml)),
-    fs.writeFile(README_PATH, newReadme),
-    fs.writeFile(ENV_PATH, newEnv),
-    fs.writeFile(PACKAGE_JSON_PATH, newPackageJson),
-    fs.copyFile(
-      path.join(rootDirectory, "remix.init", "gitignore"),
-      path.join(rootDirectory, ".gitignore")
-    ),
-    fs.rm(path.join(rootDirectory, ".github/ISSUE_TEMPLATE"), {
-      recursive: true,
-    }),
-    fs.rm(path.join(rootDirectory, ".github/PULL_REQUEST_TEMPLATE.md")),
-  ]);
+  await fs.writeFile(ENV_PATH, newEnv);
+
+  // delete files only needed for the template
+  const filesToDelete = [
+    ".github/ISSUE_TEMPLATE",
+    ".github/PULL_REQUEST_TEMPLATE.md",
+  ];
+  for (const file of filesToDelete) {
+    await fs.rm(path.join(rootDirectory, file), { recursive: true });
+  }
+
+  // replace "blues-stack-template" in all files with the app name
+  const filesToReplaceIn = [
+    "README.md",
+    "package.json",
+    "fly.toml",
+    "project.json",
+    "nx.json",
+  ];
+  for (const file of filesToReplaceIn) {
+    const filePath = path.join(rootDirectory, file);
+    const contents = await fs.readFile(filePath, "utf-8");
+    const newContents = contents.replace(/blues-stack-template/g, APP_ID);
+    await fs.writeFile(filePath, newContents);
+  }
 
   console.log(
     `
diff --git a/remix.init/package.json b/remix.init/package.json
index b8fe14b4..d219d9f2 100644
--- a/remix.init/package.json
+++ b/remix.init/package.json
@@ -3,8 +3,5 @@
   "private": true,
   "main": "index.js",
   "license": "MIT",
-  "dependencies": {
-    "@iarna/toml": "^2.2.5",
-    "sort-package-json": "^1.55.0"
-  }
+  "dependencies": {}
 }