| | |

Parcel 05 ๐Ÿ“ฆ Production Build โ€” parcel build & output directory

โฑ๏ธ Estimated read: 12 min | |

parcel build is the command that turns a Parcel project into deployable static assets. Unlike the development server, which watches files and serves them with hot module replacement, the build command runs once, applies every production optimization Parcel knows about, writes the result to disk, and exits. The output lands in a directory called dist by default, and that directory is what you upload to a static host, a CDN, or a container.

This chapter covers what parcel build actually does, why the build step exists as a separate command, how the output directory is determined and overridden, and how targets interact with distDir. It also walks through the flags that control optimization, hashing, and source maps, because a production build is only useful if you understand what it produced and why.

Key point: parcel build is not “the dev server without watching.” It is a different execution mode that enables scope hoisting, minification, dead code elimination, and content hashing by default, and it writes immutable files to dist/ rather than serving from memory.


Why parcel build exists

The dev server is not a build. During development, Parcel serves modules over HTTP with hot module replacement, skips minification for speed, and keeps source maps inline. None of that is suitable for production: the output is unminified, unbundled in places, and served with development-only runtime code. parcel build exists because production needs a different pipeline, not a different port .

Optimization changes the module graph. Production mode enables scope hoisting, which merges modules into a single scope where possible to reduce function wrapping overhead. It also runs dead code elimination and minification through Terser for JavaScript, CSSNano for CSS, and HTMLNano for HTML . These transformations are expensive and change the shape of the output, so they are opt-in via the build command rather than always-on.

The output must be deterministic and cacheable. Production assets are served with long cache lifetimes, which means filenames must change when content changes. Parcel adds content hashes to filenames during build (e.g., index.a1b2c3.js) so that a CDN can cache aggressively while still picking up new versions . The dev server cannot do this because it serves from memory and must support hot replacement.

Deployment targets require a directory, not a server. A static host needs files on disk. parcel build writes a complete, self-contained output tree to dist/, including processed HTML, hashed JavaScript and CSS, and transformed assets like images. That directory is the artifact you deploy .


a. The build command and its defaults

Running parcel build with an entry point performs a single production build and exits . The entry can be an HTML file, a JavaScript file, or a directory containing a package.json with a source field. If you omit the entry entirely, Parcel reads the source field from the package.json in the current working directory.

# Build from an explicit entry point
parcel build src/index.html

# Build using the source field in package.json
parcel build

# Build a library entry
parcel build src/index.js

When the build finishes, Parcel has written a production bundle to dist/. The HTML entry has been processed, script tags have been rewritten to point at hashed bundle filenames, and CSS referenced from JavaScript has been extracted into its own file. The command then exits, which makes it suitable for CI pipelines and npm scripts.

b. The output directory: dist and distDir

The default output directory is dist . This is not a hardcoded path; it is the default value of the distDir option, which can be set per target in package.json or overridden globally on the command line.

{
  "targets": {
    "default": {
      "distDir": "dist"
    },
    "modern": {
      "distDir": "dist/modern"
    }
  }
}

When multiple targets are defined, Parcel writes each target’s output to dist/${targetName} by default. A target named modern lands in dist/modern, and a target named legacy lands in dist/legacy . This separation is deliberate: modern and legacy bundles must not overwrite each other, and the target name becomes the subdirectory name automatically.

The --dist-dir flag overrides the output directory for the entire build, acting as the default for any target that does not specify its own distDir .

# Write the build output to build/ instead of dist/
parcel build src/index.html --dist-dir build

# Short form
parcel build src/index.html -d build

The override is useful when a deployment platform expects a specific directory name, or when you want to keep multiple build variants side by side without reconfiguring targets.

c. Targets and per-target output

Targets are Parcel’s mechanism for producing multiple output formats from one source. A library might need both a CommonJS build and an ES module build; an application might need a modern bundle for evergreen browsers and a legacy bundle for older ones. Each target has its own distDir, and if a target has only a single entry, Parcel can write to an exact filename via a top-level package.json field corresponding to the target name .

{
  "source": "src/index.js",
  "main": "dist/main.js",
  "module": "dist/module.js",
  "targets": {
    "main": {
      "context": "node",
      "outputFormat": "commonjs"
    },
    "module": {
      "context": "browser",
      "outputFormat": "esmodule"
    }
  }
}

In this configuration, the main target writes to dist/main.js and the module target writes to dist/module.js. The distDir defaults to dist for each because they are single-entry targets with exact filenames specified. If you add a third target without an exact filename, it lands in dist/third by default.


Complete Example Session

// ============================================
// PART 1: PACKAGE.JSON WITH TARGETS
// ============================================
{
  "name": "parcel-build-demo",
  "version": "1.0.0",
  "source": "src/index.html",
  "targets": {
    "default": {
      "distDir": "dist"
    },
    "library": {
      "source": "src/lib.js",
      "distDir": "dist/lib",
      "outputFormat": "esmodule"
    }
  },
  "scripts": {
    "build": "parcel build",
    "build:lib": "parcel build --target library"
  }
}
// ============================================
// PART 2: RUNNING THE DEFAULT BUILD
// ============================================
# Reads the source field from package.json
# Writes to dist/ as defined by the default target
parcel build

# Output structure after build:
# dist/index.html          (processed, hashed script refs)
# dist/index.a1b2c3.js     (minified, scope-hoisted bundle)
# dist/index.d4e5f6.css    (extracted and minified styles)
// ============================================
// PART 3: BUILDING A SPECIFIC TARGET
// ============================================
# Build only the library target
parcel build --target library

# Output lands in dist/lib/ because the target
# specifies distDir: "dist/lib"
# dist/lib/lib.js          (ES module output)
// ============================================
// PART 4: OVERRIDING OUTPUT DIRECTORY
// ============================================
# Write everything to build/ instead of dist/
parcel build --dist-dir build

# The default target's distDir is overridden
# The library target still uses its explicit distDir
# unless --dist-dir is combined with target filtering
// ============================================
// PART 5: DISABLING OPTIMIZATION FOR DEBUGGING
// ============================================
# Build without minification or content hashing
parcel build --no-optimize --no-content-hash

# Useful when inspecting the raw bundle structure
# Filenames lose hashes: dist/index.js instead of index.a1b2c3.js
// ============================================
// PART 6: CONTROLLING SOURCE MAPS
// ============================================
# Exclude source maps from the production output
parcel build --no-source-maps

# Source maps are useful for error reporting but
# expose original source structure; disable for
# public-facing production if that is a concern
// ============================================
// PART 7: SETTING PUBLIC URL FOR SUBDIRECTORY DEPLOY
// ============================================
# If deploying to https://example.com/app/
parcel build --public-url /app/

# All asset URLs in HTML and CSS are prefixed
# with /app/ so absolute references resolve correctly
// ============================================
// PART 8: INSPECTING THE OUTPUT
// ============================================
# After build, list the dist directory
ls -la dist/

# Expected entries:
# index.html
# index.*.js
# index.*.css
# Any transformed images or fonts
// ============================================
// PART 9: CI SCRIPT EXAMPLE
// ============================================
# package.json script for CI
# "build:ci": "parcel build --no-source-maps --dist-dir public"

# Run in CI
npm run build:ci

# Deploy the public/ directory to the static host
// ============================================
// PART 10: CLEANING CACHE BETWEEN BUILDS
// ============================================
# Parcel caches in .parcel-cache/
# If a build produces stale output, clear the cache
rm -rf .parcel-cache dist
parcel build

# The cache is safe to delete; it regenerates
# on the next build at the cost of a slower run

The ten parts covered the full lifecycle of a Parcel production build: declaring targets in package.json, running the default build, filtering by target, overriding the output directory, disabling optimization for inspection, controlling source maps, setting a public URL for subdirectory deployment, inspecting the result, scripting for CI, and clearing cache when necessary.


Quick Reference

The build command

CommandEffect
parcel buildSingle production build, exits when done
parcel build <entry>Build from explicit entry file
parcel build --target <name>Build only the named target
parcel build --dist-dir <dir>Override output directory for all targets
parcel build --no-optimizeDisable minification and scope hoisting
parcel build --no-content-hashFilenames without hashes

Output directory resolution

ConditionOutput location
Single target, no distDirdist/
Multiple targets, no distDirdist/${targetName}/
Target specifies distDirThat path
--dist-dir flag providedFlag value overrides default
Exact filename in package.json fieldThat filename in dist/

Optimization flags

FlagDefault in buildDisable with
MinificationEnabled--no-optimize
Scope hoistingEnabled--no-optimize
Content hashingEnabled--no-content-hash
Source mapsEnabled--no-source-maps

Cache location

ItemPath
Parcel cache.parcel-cache/
Build outputdist/ (unless overridden)

Best Practices

โœ… Do This:

# Use --dist-dir in CI to match deployment expectations
parcel build --dist-dir public                    # โœ…
# Filter by target when building a library variant
parcel build --target library                     # โœ…
# Disable source maps for public production if source exposure matters
parcel build --no-source-maps                     # โœ…
# Set public-url when deploying to a subdirectory
parcel build --public-url /app/                   # โœ…
# Clear .parcel-cache in CI for reproducible builds from scratch
rm -rf .parcel-cache && parcel build              # โœ…
# Keep dist/ out of version control via .gitignore
echo "dist/" >> .gitignore                        # โœ…

โŒ Don’t Do This:

# Use the dev server for production output
parcel src/index.html                             # โŒ (serves, not builds)
# Rely on dist/ existing without running build
# Deploying a stale dist is worse than no deploy
scp dist/* server:/var/www/                       # โŒ (without fresh build)
# Leave content hashing enabled but cache HTML forever
# Hashes change; HTML must be revalidated
# Cache-Control: max-age=31536000 on index.html   # โŒ
# Use --no-optimize in production
parcel build --no-optimize                        # โŒ (ships unminified code)
# Assume distDir applies to all targets equally
# A target with explicit distDir ignores --dist-dir
parcel build --dist-dir out                       # โŒ if targets set distDir
# Commit .parcel-cache to version control
git add .parcel-cache/                            # โŒ (regenerated, large)

Common Pitfalls

PitfallWhy It HappensFix
Build output missing from expected directoryA target defines its own distDir, overriding --dist-dirCheck all targets; use --dist-dir only when targets do not set distDir
Old files persist in dist/ after buildParcel writes new files but does not clean the directoryRemove dist/ before building in CI; Parcel does not do a full clean by default
Hashed filenames break HTML referencesManual script tags in HTML are not processedLet Parcel inject bundle references; do not hardcode hashed names
Build fails with “entry not found”No entry argument and no source field in package.jsonProvide entry explicitly or add source to package.json
Source maps expose original codeSource maps enabled by default in productionAdd --no-source-maps when deploying publicly
Assets 404 after deploy to subdirectoryAbsolute paths point to domain rootSet --public-url /subdir/ during build
Build takes long on every CI runCache is not persisted between CI jobsCache .parcel-cache/ between runs, or accept cold build cost

Real-World Examples

1. Static site with HTML entry

parcel build src/index.html

2. Library with CommonJS and ES module targets

{"targets": {"main": {"outputFormat": "commonjs"}, "module": {"outputFormat": "esmodule"}}}

3. Deploying to GitHub Pages subdirectory

parcel build --public-url /repo-name/

4. CI build without source maps

parcel build --no-source-maps

5. Inspecting unminified output

parcel build --no-optimize --no-content-hash

6. Building only the legacy target

parcel build --target legacy

7. Writing to a platform-specific directory

parcel build --dist-dir public

8. Clearing cache before a release build

rm -rf .parcel-cache dist && parcel build

9. Build script in package.json

{"scripts": {"build": "parcel build --dist-dir dist"}}

10. Multi-target output structure

parcel build && ls dist/modern dist/legacy

Visual

The build pipeline from source to dist

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PARCEL BUILD PIPELINE                                       โ”‚
โ”‚                                                              โ”‚
โ”‚  src/index.html โ”€โ”€โ–บ Asset graph resolution                   โ”‚
โ”‚       โ”‚                    โ”‚                                 โ”‚
โ”‚       โ–ผ                    โ–ผ                                 โ”‚
โ”‚  src/index.js โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Transform (Babel, PostCSS, etc.)       โ”‚
โ”‚       โ”‚                    โ”‚                                 โ”‚
โ”‚       โ–ผ                    โ–ผ                                 โ”‚
โ”‚  src/style.css โ”€โ”€โ”€โ”€โ”€โ–บ Scope hoisting + tree shaking          โ”‚
โ”‚                            โ”‚                                 โ”‚
โ”‚                            โ–ผ                                 โ”‚
โ”‚                     Minification (Terser, CSSNano)           โ”‚
โ”‚                            โ”‚                                 โ”‚
โ”‚                            โ–ผ                                 โ”‚
โ”‚                     Content hashing                          โ”‚
โ”‚                            โ”‚                                 โ”‚
โ”‚                            โ–ผ                                 โ”‚
โ”‚                     dist/ (written to disk)                  โ”‚
โ”‚                                                              โ”‚
โ”‚  The build runs once, writes files, and exits.               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Output directory resolution order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  WHICH DIRECTORY DOES THE OUTPUT GO TO?                      โ”‚
โ”‚                                                              โ”‚
โ”‚  Start: parcel build --dist-dir <flag>                       โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Is --dist-dir provided? โ”€โ”€yesโ”€โ”€โ–บ Use flag value             โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       no                                                     โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Does the target set distDir? โ”€โ”€yesโ”€โ”€โ–บ Use target distDir    โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       no                                                     โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Multiple targets? โ”€โ”€yesโ”€โ”€โ–บ dist/${targetName}/              โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       no                                                     โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Default: dist/                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The dist directory after a typical build

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  dist/                                                       โ”‚
โ”‚                                                              โ”‚
โ”‚  index.html              โ† processed, hashed refs injected   โ”‚
โ”‚  index.a1b2c3.js         โ† minified, scope-hoisted bundle    โ”‚
โ”‚  index.d4e5f6.css        โ† extracted, minified styles        โ”‚
โ”‚  image.7g8h9i.png        โ† transformed asset with hash       โ”‚
โ”‚                                                              โ”‚
โ”‚  Filenames change when content changes.                      โ”‚
โ”‚  HTML references are rewritten to match.                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Target with explicit distDir vs default

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TARGET OUTPUT SEPARATION                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  package.json targets:                                       โ”‚
โ”‚    default   โ†’ distDir: "dist"     โ†’ dist/                   โ”‚
โ”‚    library   โ†’ distDir: "dist/lib" โ†’ dist/lib/               โ”‚
โ”‚                                                              โ”‚
โ”‚  The --dist-dir flag overrides only targets that do not      โ”‚
โ”‚  declare their own distDir.                                  โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                          โ”‚
โ”‚  โ”‚   dist/     โ”‚    โ”‚  dist/lib/  โ”‚                          โ”‚
โ”‚  โ”‚  index.html โ”‚    โ”‚  lib.js     โ”‚                          โ”‚
โ”‚  โ”‚  index.js   โ”‚    โ”‚             โ”‚                          โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Commandparcel build โ€” single production build, exits
Default outputdist/
Output override--dist-dir <dir>
Multi-target defaultdist/${targetName}/
Per-target overridedistDir field in target config
MinificationEnabled by default in build
Scope hoistingEnabled by default in build
Content hashingEnabled by default in build
Source mapsEnabled by default; disable with --no-source-maps
Cache location.parcel-cache/ (safe to delete)
Public URL--public-url for subdirectory deploys

Key takeaways:

  • parcel build is a distinct mode. It enables scope hoisting, minification, and content hashing that the dev server skips for speed.
  • dist is the default, not the rule. The actual output directory comes from a resolution chain: --dist-dir flag, then target distDir, then dist/${targetName} for multiple targets, then dist.
  • Targets write to separate subdirectories by default. This prevents modern and legacy bundles from overwriting each other and makes the output structure predictable.
  • Hashed filenames require cache discipline. HTML must not be cached forever because it references hashed files that change per build.
  • --dist-dir does not always win. A target with an explicit distDir ignores the global flag, which is a common source of confusion in multi-target projects.
  • Source maps are on by default. If exposing original source structure is a concern, --no-source-maps is required for public production.
  • Parcel does not clean dist/ automatically. Stale files from previous builds can persist; remove the directory in CI for reproducible output.
  • --public-url is mandatory for subdirectory deploys. Without it, absolute asset paths resolve to the domain root and return 404s.

Remember: parcel build produces the artifact you deploy. It runs once, applies production optimizations by default, and writes to dist/ unless a target or flag redirects it. The output directory is not a detail to ignore: hashed filenames, target separation, and subdirectory deployment all depend on knowing exactly where Parcel wrote its files and how the HTML references them. Treat dist/ as immutable, deploy it to static hosting, and never confuse the dev server with a production build.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!