Skip to content

Expert · Build, deploy and get hired · topic 61 of 64 · 30 min

Building for production

Explanation

The build step turns your source code into small, fast files for real users: it bundles, minifies, removes unused code and adds a hash to file names for caching. In a Vite project, npm run build writes these files to the dist folder and npm run preview lets you test them. Only VITE_ variables reach the browser, so never put secrets in them.

What a build step does

While you work, npm run dev serves your source files almost as they are, so changes appear instantly. Real users need something different: as few and as small files as possible, which their browser can keep (cache) for a long time. The build step makes that version. You set up Vite and npm scripts in the intermediate module on tools; here you use them for real.

Think of it like this: Think of cooking at home versus packing a tiffin for office. In the kitchen, everything is spread out so you can taste and change things (the dev server). For the tiffin you pack only what is needed, tightly, in labelled boxes that travel well (the build).
Build jobWhat it meansWhy it helps
BundlingJoins your many modules (and npm packages) into a few filesFewer requests for the browser
MinifyingRemoves spaces and comments, shortens variable namesSmaller downloads
Tree shakingDrops exported code that nothing importsUnused code never reaches the user
Hashing file namesindex.js becomes something like index-B3x9kQ1a.js; the letters change when the content changesBrowsers can cache files for a long time safely
Copying public/Files in the public folder are copied as they arefavicon.svg, robots.txt, images you link by exact path

npm run build and the dist folder

bash
npm run build
# runs "vite build" (see the scripts in package.json)

# the result:
dist/
  index.html
  favicon.svg
  assets/
    index-B3x9kQ1a.js
    index-D8fL2pZc.css
The hash letters here are only examples; yours will differ

dist is generated. Never edit files in it by hand: change the source and build again. The Vite starter already lists dist in .gitignore, because the host builds it again from your code. Why do hashed names help? The host can tell the browser "keep the files in assets for a year". When you change your code, the build makes new names, so the browser downloads the new files. index.html itself is not cached for long, so it always points to the newest hashed files.

Preview the build on your computer

bash
npm run build
npm run preview
# serves the dist folder, usually at http://localhost:4173
Always test the real build before you deploy it
Tip: Do not double-click dist/index.html. Opening it as a file (file://) usually shows a blank page, because the built page loads its files from paths like /assets/... that only work on a web server. npm run preview is that server.

Environment variables in Vite

Some values change between your laptop and the live site, such as the address of an API. Keep them in .env files and read them with import.meta.env. Vite only exposes variables whose names start with VITE_ to your browser code.

javascript
// .env.production contains:  VITE_API_URL=https://api.example.com
const apiUrl = import.meta.env.VITE_API_URL;

console.log(import.meta.env.MODE); // "development" or "production"
if (import.meta.env.DEV) {
  console.log("Extra logs only while developing");
}
import.meta.env.VITE_... is replaced by the real value at build time
Careful: Everything that reaches the browser is public. Anyone can open DevTools or the built JS file and read a VITE_ value. Never put a payment secret, database password or private API key in any VITE_ variable. Secrets belong on a server. Keep personal values in .env.local, which the Vite starter keeps out of Git.

Base path: hosting in a subfolder

By default the built page loads /assets/... from the root of the domain. If your site will live at https://asha.github.io/expense-tracker/, the files are really under /expense-tracker/, so the page stays blank until you tell Vite the base path.

javascript
// vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  base: "/expense-tracker/", // starts and ends with /
});
In your code, import.meta.env.BASE_URL gives this value

Check the size, then check everything else

After a build, Vite prints every file with its size and its gzip size (gzip is the compressed size that actually travels). On a cheap phone on mobile data, a large JS file is slow. Look for one huge file, a big library used for one small job, and images of several MB. The fixes are the ones from the quality module: compress images, lazy load, import only what you use, and split rarely used pages with dynamic import().

  • Lighthouse (DevTools, Lighthouse panel) on the preview, in mobile mode: fix every red item.
  • No broken links: click every menu link and every project link.
  • A favicon shows in the browser tab.
  • A friendly 404.html page with a link back home.
  • A title and meta description on every page, plus Open Graph tags so links look good on WhatsApp and LinkedIn.
  • No errors in the Console, and it works on a real phone.
  • No secrets in the build: search the dist folder for words like "secret" and "key".

Watch the lesson

Building for production · 1/8

npm run dev = for you npm run build = for users

The dev server is for you. Real users need a build: small, fast files that cache well.

Prefer a video by a teacher? Find free videos on “vite build for production dist preview environment variables” on YouTube ↗

In real life: The blank page after deploy

Ravi built a fee calculator for his tuition centre. It worked with npm run dev, but after he put it on GitHub Pages the page was blank. The Console showed 404 errors for /assets/index-....js: the site lived at /fee-calculator/, so he set base: "/fee-calculator/" and rebuilt. npm run preview had already warned him that the build behaved differently from dev. Lighthouse then found a 2 MB banner photo; compressing it to WebP took the page from slow to fast on 4G.

Practice (0/3 done)

Task 1 · Code · Javascript

Vite only gives browser code the variables whose names start with "VITE_" (capital letters). Write a function publicEnv(env) that receives an object of all variables and returns a new object with only the keys that start with "VITE_". publicEnv({ VITE_API_URL: "https://api.example.com", PAYMENT_SECRET: "abc" }) returns { VITE_API_URL: "https://api.example.com" }.

Task 2 · Code · Javascript

Write a function withBase(base, path) that joins a base path and a file path with exactly one "/" between them. base always starts with "/" but may or may not end with "/"; path may or may not start with "/". withBase("/shop", "cart.html") returns "/shop/cart.html", withBase("/shop/", "/cart.html") also returns "/shop/cart.html", and withBase("/", "about.html") returns "/about.html".

Task 3 · Practice

Take a small Vite project (one from the tools module, or a new one made with npm create vite@latest). Build it, preview it and inspect what the build made. Add one VITE_ variable and prove to yourself that it is public. Then pretend the site will live at /my-app/: set base, rebuild and preview again. Finish with a Lighthouse run on the preview in mobile mode.

Quick quiz

Question 1

1.What does minifying do?

Question 2

2.Why does the build name a file index-B3x9kQ1a.js instead of index.js?

Question 3

3.Which variable can browser code read with import.meta.env in Vite?

Question 4

4.Your site will live at https://asha.github.io/notes-app/. What should base be in vite.config.js?

Question 5

5.What is the right way to test the production build on your computer?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Effects, data fetching and hooks