Cloud & Platform Engineering
Safely Migrating a Next.js Development Environment from Windows to macOS
A practical recovery workflow for moving a Next.js project to Apple Silicon macOS while preserving Git history, private local files, and build reproducibility.
Moving a development project to another operating system involves more than copying a folder. The source code may appear intact while Git metadata, hidden configuration, platform-specific native packages, or private local files are missing. In that state, the project is not yet a reproducible development environment.
This article describes the recovery sequence used to move a Next.js 15 project from Windows to an Apple Silicon Mac. The goal was not merely to start the development server. It was to preserve both the remote repository baseline and local-only assets, align the Node.js version with CI, and complete the full validation pipeline.
Define completion before changing the project
Running npm install immediately may fix the first visible error, but it also makes it harder to tell what was lost during the move. I started by defining a migration completion checklist.
- Confirm that the working volume uses a stable, writable filesystem.
- Check whether hidden paths such as
.git,.gitignore, and CI workflows were preserved. - Verify the existence, not the contents, of local documents and infrastructure settings excluded from Git.
- Match the Node.js major version used by CI.
- Reinstall dependencies for the target operating system without replacing the lockfile.
- Run the content audit, lint, typecheck, and production build.
Sensitive files do not need to be printed during diagnosis. Their existence and ignore status are enough to validate preservation without exposing their contents in logs.
Treat missing hidden files as a migration failure
The copied project contained package.json, src, and content, but Git did not recognize it as a repository. Inspection showed that .git, .gitignore, and the CI configuration were all missing. This was evidence that the copy process had excluded hidden items as a group rather than losing one isolated file.
Running git init at this point would create a new repository and discard the relationship with the original commit history. Instead, I cloned the remote repository into a separate temporary directory and verified the baseline with:
git status --short --branch
git branch --show-current
git log -5 --oneline --decorateOnly after the remote HEAD matched the pre-migration record did I use it as the recovery baseline. The merge back into the working directory did not use a delete option. Local work records and infrastructure configuration absent from the remote therefore remained in place while Git history and tracked files were restored.
Create a recovery point before overlaying files
Even when the remote is authoritative, overwriting the current copy without a fallback is risky. I backed up only the current files that corresponded to the remote tracked-file list before applying the baseline.
Current project copy
├─ tracked-file backup
├─ ignored private files (preserved in place)
└─ remote baseline overlay (no delete)This approach reduces two risks:
- Tracked files changed or damaged during migration remain available for comparison.
- Local-only files are not replaced or deleted just because they do not exist in the remote clone.
After recovery, I verified the branch, HEAD, origin/main, and recent commits again. I also set the repository-local core.autocrlf value to input so line-ending conversion would not create a large unrelated diff.
Reinstall Node.js and native dependencies for macOS
A Windows node_modules directory should not be copied to macOS. Next.js SWC and the Sharp image library select binaries for a specific operating system and CPU architecture.
After installing the same Node.js 22 major version used by CI, I kept the existing package-lock.json and ran:
npm ciI then verified that the Apple Silicon SWC package and the macOS Sharp package were installed. I did not regenerate the lockfile or manually restore Windows binaries.
A dependency audit may propose security patches during migration. I avoided applying --force automatically. Breaking upgrades and changes that rewrite a large portion of the lockfile are easier to review as a separate dependency-upgrade task than as part of operating-system recovery.
Use the full validation pipeline as the finish line
A single page loading in a browser is not enough to prove that the migration succeeded. I ran the complete project checks:
npm run audit:posts
npm run lint
npm run typecheck
npm run build
npm audit --omit=devThe production build exercises static-page generation, MDX parsing, type validation, and image-processing paths together. In this project it generated 128 static pages, matching the result from the previous environment.
Sandboxed tooling can cause Turbopack to fail when a helper process tries to bind to a local port. If the error contains both binding to a port and a permission denial, repeat the same build in the normal local environment before treating it as an application bug.
Replace machine-specific paths with portable resolution
One helper script still contained a Windows absolute path. Replacing it with a hard-coded macOS volume name would only move the problem to another machine. I changed the script to prefer an environment variable and otherwise resolve a sibling project directory.
const sourceRoot = path.resolve(
process.env.EXTERNAL_PROJECT_ROOT ??
path.join(projectRoot, "..", "ExternalProject"),
);If the external project is unavailable, the main blog build should still work. Only the optional synchronization command should report that its source is missing. Windows paths embedded in tutorials or lab examples do not need a repository-wide replacement.
Practical rules from the migration
This migration was closer to an integrity check than a file-copy operation. The most useful rules were:
- Treat the remote repository as the baseline for tracked files, not as a backup for private local assets.
- Preserve the current tracked files before applying the remote baseline.
- Overlay without deletion, then use Git status to explain the result.
- Reinstall platform-specific dependencies through the lockfile and
npm ci. - Make the content audit and production build part of the completion criteria.
- Keep security upgrades separate when they introduce breaking changes or broad lockfile churn.
The migration is complete only when Git history, local-only assets, CI versions, native packages, and the full build are all in a state that can be explained and reproduced.
Related writing
Cloud & Platform Engineering
Azure CLI, PowerShell, and SQL Server Operations Automation Flow
A practical operations note connecting Azure CLI, PowerShell, VM provisioning, SQL Server setup, and repeatable infrastructure tasks.
Cloud & Platform Engineering
Static Hosting with S3, CloudFront, and Terraform
A small infrastructure project that provisions static hosting with S3, CloudFront, Route 53, and Terraform.
Cloud & Platform Engineering
Cloud Network Addressing and Routing Foundations
A network fundamentals note covering CIDR, subnetting, gateways, routing, DNS, TLS, and cloud network boundaries.