Safeguard
Developer Tools•

Does a theme build step put node_modules on a production site?

A theme build with npm should ship compiled assets only. Here is how to check that node_modules and dev tools stay off a live WordPress server.

Safeguard Security Team
4 min read

It should not. A theme or plugin that uses npm for its build should ship only the compiled CSS, JavaScript and images to production. If node_modules, package.json or build scripts reach the live server, you have added code and attack surface that the site does not need.

This is a common pattern in agency work. The theme is built with a bundler, and someone zips the whole folder and uploads it.

Why it matters

Build tools run code on your machine or CI server when you install them. They also pull in a deep tree of transitive packages. None of that is needed to serve a page. On a live server it is only files that can be found, read or misused, and files that nobody patches because nobody knows they are there.

A pre-launch checklist

  1. Run the build in CI from a clean checkout using npm ci. The npm documentation describes it as an install meant for automated environments, requiring an existing lockfile and exiting with an error if the lockfile does not match package.json.
  2. Package the output, not the project folder. Include built assets, PHP templates and the files WordPress needs.
  3. Exclude node_modules, test folders, source maps you do not want public, and package manager files from the artifact.
  4. Deploy the artifact, not a copy of the working directory.
  5. Check the live server: list the theme directory and confirm nothing from the build tree is present.

What to check on the server

ItemPresent on production?Action if yes
node_modules directoryShould be noRemove and fix the deploy
package.json and lockfileUsually noRemove from artifact unless needed
Source mapsDecide on purposeRemove if they expose private source
.git directoryShould be noRemove and block access
Build config filesUsually noExclude from artifact

Lockfiles still belong in the repository

Keep package-lock.json in version control even if it does not ship. It is how CI and teammates get the same versions. Composer's documentation makes the same argument for composer.lock for applications: everyone runs the same dependencies, and reinstalling in six months gives the same result.

Scan the right thing

A scan of the repository tells you what the build uses. A scan of the live folder tells you what you shipped. Do both occasionally. If the live folder contains a dependency tree, that is a finding by itself.

Handling install scripts

Packages can run scripts during install. Consider whether your CI needs them, and review what a new package adds before merging. This applies to build time, not production, so the main protection is a clean separation between build and serve.

Try it yourself

Click through it at your own pace, or open it full screen. Look at how many packages sit under a handful of direct dependencies, which is why build trees should not be uploaded.

Safeguard scans package manifests and generates an SBOM, and the free scan is a way to see the tree for a theme repository.

Quick check

Should package-lock.json be committed for a theme?

Yes, in the repository. It does not need to be uploaded to the live server.

Is it a problem if node_modules is on the server but not linked anywhere?

It is still unnecessary code on disk. Remove it, since unlinked files can often be requested directly.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.