Build web applications with less tooling and 10x faster iteration.

$ npx snowpack


How It Works

  1. Instead of bundling on every change, just run Snowpack once right after npm install.
  2. Snowpack re-installs your dependencies as single JS files to a new web_modules/ directory. It never touches your source code.
  3. Write code, import those dependencies via an ESM import, and then run it all in the browser.
  4. Skip the bundle step and see your changes reflected in the browser immediately after hitting save.
  5. Keep using your favorite web frameworks and build tools! Babel & TypeScript supported.

how it works illustration

// In a Snowpack application, this runs directly in the browser!
import React from '/web_modules/react.js';

// In a Snowpack application /w Babel:
import React from 'react';


Who Should Use Snowpack?

Who Should Avoid Snowpack?

Modern Browser Support

Data on support for the es6-module feature across the major browsers from

By default, Snowpack installs npm dependencies as ES Module (ESM), which run natively wherever ESM syntax is supported. This includes ~90% of all browsers in use today. All modern browsers (Firefox, Chrome, Edge, Safari) have supported ESM since early 2018.

Additionally, Snowpack runs all dependencies through Babel via @preset/env to transpile any less-supported language features found in your dependencies. You can customize this behavior by setting your own “browserslist” key in your package.json manifest (see below).

Legacy Browser Support

legacy browser icons

As of v1.1, Snowpack also supports legacy browsers via the the --nomodule flag! This is a production-only flag that generates a legacy bundle of your entire application for older browsers that don’t understand ESM syntax. Because your application is already built to run in the browser, there’s no configuration or extra plugins required to use it.

If you need to support legacy browsers like IE11, UC Browser for Android (popular in China and India), Samsung Mobile, and Opera Mini then be sure to enable this feature.

Note that this introduces a production build step into your deployment process, if you don’t already have one. This is in addition to Snowpack’s “run-only-once-at-install-time” normal behavior, but still much lighter than Webpack/Parcel’s “run-on-every-change-at-dev-time” normal behavior.

Read our guide on Supporting Legacy Browsers for more details.

Load Performance

You can think of Snowpack like an optimized code-splitting strategy for your dependencies. Dependencies are bundled into individual files at install-time, with all internal package files bundled together as efficiently as possible. Any common, shared dependencies are moved into common, shared chunks in your web_modules/ directory.

But make no mistake: unbundled applications have a different performance story than bundled applications. Cache efficiency improves significantly (especially useful if you deploy multiple times per day) and the risk of shipping duplicate code across bundles goes to zero.

Unlike in traditional bundled applications, long (7+) chains of imports can slow down your first page load. How you weigh these pros and cons of unbundled production applications depends on your specific application and use-case.

Of course, you’re always free to add a bundler as a part of your production build pipeline only and you’ll continue to get the developer experience boost. Or, just use Snowpack to get started and then add a bundler whenever you feel you need to for performance. That’s what we did when we started, and years later performance is still good.

To learn more about unbundled load performance, check out Max Jung’s post on “The Right Way to Bundle Your Assets for Faster Sites over HTTP/2”. It’s the best study on HTTP/2 performance & bundling we could find online, backed up by real data. Snowpack’s installation most closely matches the study’s moderate, “50 file” bundling strategy. Jung’s post found that for HTTP/2, “differences among concatenation levels below 1000 [small files] (50, 6 or 1) were negligible.”

Cache Performance

Snowpack performs best when it comes to caching. Snowpack keeps your dependencies separate from your application code and from each other, which gives you a super-optimized caching strategy by default. This lets the browser cache dependencies as efficiently as possible, and only fetch updates when individual dependencies are updated.

The same applies to unbundled application code as well. When you make changes to a file, the browser only needs to re-fetch that one file. This is especially useful if you manage multiple deployments a day, since only changed files need to be loaded.

Get Started

Installing Snowpack

npm install --save-dev snowpack
(or) yarn add --dev snowpack

# Then, run Snowpack:
npx snowpack

Installing a Dev Server

Snowpack is agnostic to how you serve your site during development. If you have a static dev server that you already like, use it to serve your Snowpack app. Otherwise, we recommend one of the following for local development:

Quick Start

0. First, create a new project directory

mkdir snowpack-demo
cd snowpack-demo
npm init --yes

1. Install your dependencies

npm install --save preact htm

We’ll start this tutorial by using Preact (similar to React) & HTM (similar to JSX). Even if you only want to use React and JSX, you should still start here. By the end of this tutorial, we’ll show how you can optionally replace Preact with React, and HTM with JSX (via Babel).

2. Run Snowpack to create your web_modules/ directory

npx snowpack
✔ snowpack installed: preact, htm. [0.50s]

If all went well, you should see a web_modules/ directory containing the files preact.js & htm.js. This is the magic of Snowpack: your application can import these files directly and the browser will run them with no additional tooling needed!

Optionally, you can now run npm install snowpack --save-dev to speed up future Snowpack runs. Otherwise, npx tends to re-install the tool before every run.

3. Create a simple HTML file for your application:

<!-- File Location: index.html -->
<!DOCTYPE html>
<html lang="en">
<head><title>Snowpack - Simple Example</title></head>
<div id="app"></div>
<script type="module" src="/src/app.js"></script>

4. Create a simple JavaScript application:

/* File Location: src/app.js */
// Import your web-ready dependencies
import { h, Component, render } from '/web_modules/preact.js';
import htm from '/web_modules/htm.js';
const html = htm.bind(h);
// Create your main app component
function SomePreactComponent(props) {
return html`<h1 style="color: red">Hello, World!</h1>`;
// Inject your application into the an element with the id `app`.
render(html`<${SomePreactComponent} />`, document.getElementById('app'));

5. Serve & run your application

npx servor --reload

Start up a simple dev server (we recommend servor with the --reload flag for live-reload). Open your web browser and see your application running directly in the browser, instantly!

Any changes that you make to your src/app.js file are immediately reflected via either live-reload (if supported by your dev server) or a manual browser refresh. No bundlers, no build steps, and no waiting around for things to re-build after you make a change.

Open up your browser’s Dev Tools and debug your application directly in the browser. Browse your source code, set breakpoints, and get more useful error messages.

6. Optional Next Steps


Check out our repo’s README for a full list of example applications built with Snowpack:

Bootstrap a Starter App

🆕 Check out snowpack-init! Instantly bootstrap a starter app with Snowpack. Choose between templates for Preact, Lit-HTML, TypeScript, and more.

Basic Usage

Snowpack has a single goal: to install web-ready npm packages to web_modules/ directory. It doesn’t touch your source code. What you build with it, which frameworks you use, and how you serve your project locally is entirely up to you. You can use as many or as few tools on top of Snowpack as you’d like.

Still stuck? See our Quick Start guide above for help to get started.

Zero-Config Installs (Default)

$ npx snowpack

By default, Snowpack will attempt to install all “dependencies” listed in your package.json manifest. If the package defines an ESM “module” entrypoint, then that package is installed into your new web_modules/ directory.

As long as all of your web dependencies are listed as package.json “dependencies” (with all other dependencies listed under “devDependencies”) this zero-config behavior should work well for your project.

Automatic Installs (Recommended)

$ npx snowpack --include "src/**/*.js"

With some additional config, Snowpack is also able to automatically detect dependencies by scanning your application for import statements. This is the recommended way to use Snowpack, since it is both faster and more accurate than trying to install every dependency.

To enable automatic import scanning, use the --include CLI flag to tell Snowpack which files to scan for. Snowpack will automatically scan every file for imports with web_modules in the import path. It will then parse those to find every dependency required by your project.

Remember to re-run Snowpack every time you import an new dependency.

Whitelisting Dependencies

  /* package.json */
"snowpack": {
"webDependencies": [
"preact/hooks", // A package within a package
"unistore/full/", // An ESM file within a package (supports globs)
"bulma/css/bulma.css" // A non-JS static asset (supports globs)

Optionally, you can also whitelist any dependencies by defining them in your “webDependencies” config (see below). You can use this to control exactly what is installed, including non-JS assets or deeper package resources.

Note that having this config will disable the zero-config mode that attempts to install every package found in your package.json “dependencies”. Either use this together with the --include flag, or just make sure that you whitelist everything that you want installed.

All CLI Options

Run Snowpack with the --help flag to see a list of all supported options to customize how Snowpack installs your dependencies.

npx snowpack --help

All Config Options

Note: All package.json options are scoped under the "snowpack" property.

  "dependencies": { "htm": "^1.0.0", "preact": "^8.0.0", /* ... */ },
"snowpack": {
"webDependencies": [
"dedupe": [

Advanced Usage

Importing Packages by Name

import 'package-name';

Browsers only support importing by URL, so importing a package by name (ex: import React from 'react') isn’t supported without additional tooling/configuration. Unless you’re using a traditional app bundler or a build tool like Babel, you’ll need to import all dependencies in your application by URL (ex: import React from '/web_modules/react.js').

If you use Babel with Snowpack, you can use our Babel plugin to support package name imports. The plugin reads any packages name imports in your source code and rewrites them to full "/web_modules/${PACKAGE_NAME}.js" URLs that run in the browser. This way, you can keep using the package name imports that you’re used to without needing a full web app bundler. Check out our guide below.

/* .babelrc */
"plugins": [
["snowpack/assets/babel-plugin.js", {}],

Import Maps

Warning: Import Maps are an experimental web technology that is not supported in every browser. For polyfilling import maps, check out es-module-shims.

// Include this in your application HTML...
<script type="importmap" src="/web_modules/import-map.json"></script>
// ... to enable browser-native package name imports.
import * as _ from 'lodash';

Snowpack generates an Import Map with every installation. If your browser supports Import Maps, you can load the import map somewhere in your application and unlock the ability to import packages by name natively in the browser (no Babel step required).

Production Optimization

Tree-shaking example

$ npx snowpack --optimize

By default, Snowpack installs dependencies unminified and optimized for development. When you’re ready for production, run Snowpack with the --optimize flag to enable certain production-only optimizations:

Supporting Legacy Browsers

Nomodule example

If you are building a Single Page Application (SPA), run Snowpack with --nomodule and pass in your application entrypoint. Then, create a second script tag in your application to point legacy browsers towards this legacy build alongside your modern type="module" entrypoint:

<!-- Ignored by legacy browsers: -->
<script type="module" src="/src/index.js"></script>
<!-- Ignored by modern browsers: -->
<script nomodule src="/web_modules/app.nomodule.js"></script>

After doing this, run Snowpack to generate a /web_modules/app.nomodule.js application bundle that will automatically be run on older, legacy browsers.

Similar to the --optimize flag, --nomodule must be run before every deployment. When using this flag, we strongly recommend scripting your deployments to guarantee that Snowpack is never skipped before a deployment (ex: npm run deploy or npm run build:production).

This bundle step was designed to be simple and general-purpose enough to cover any browser-ready application with zero config. But, if you do get to the point where you’d like to manually configure/control your production bundles, we’d recommend setting up a bundler yourself separately from Snowpack.

Caching in the Browser

The unbundled applications that Snowpack allows you to build can be ultra cache-efficient, with zero code duplication across page loads. But proper caching requires some helpful information from the server. Below is a list of caching strategies for your server that you can use with Snowpack.

ETag Headers

ETag support is the easiest caching strategy to implement, and many hosting providers (like Zeit) will enable this for you automatically. From MDN:

The ETag HTTP response header is an identifier for a specific version of a resource. It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed. Additionally, etags help prevent simultaneous updates of a resource from overwriting each other (“mid-air collisions”). –

Service Workers

Service workers can implement a client-side cache for your site regardless of what your server responses look like. Check out this article on "Caching Files with Service Worker ", and be sure to read our Workbox guide below for help using Workbox with Snowpack.

Automatic Cache Busting via Import URL

The browser cache is keyed by unique resource URL. This means that different URL query params will result in different cache hits (and cache misses) even if the server ignores them. Applications built with Babel can leverage this behavior to automatically control the cache.

The Snowpack Babel plugin supports an "addVersion" option that will automatically add the package version of any package import as a query parameter of the import URL in the final build. This effectively creates a new cache entry every time a dependency changes, which allows your server to send more aggressive long-term cache headers for the web_modules/ directory.

/* .babelrc */
"plugins": [
["snowpack/assets/babel-plugin.js", {"addVersion": true}],
// src/ File Input
import Foo from 'package-name';
// lib/ Babel Output
import Foo from '/web_modules/package-name.js?v=1.2.3';

Customize Transpilation

  /* package.json */
"browserslist": " >0.75%, not ie 11, not UCAndroid >0, not OperaMini all",

Snowpack runs all dependencies through Babel (via @preset/env) to transpile unsupported language features in your dependencies. This is useful when packages rely on modern language features that your users’ browsers may not support.

By default, Snowpack will transpile using the recommended target string shown above. You you can customize this behavior by setting your own top-level “browserslist” key in your package.json manifest.

Run After Every Install

  /* package.json */
"scripts": {
"prepare": "snowpack"

You can optionally add “snowpack” as a "prepare" script to your package.json and npm/yarn will automatically run it after every new dependency install. This is recommended so that new dependencies are automatically included in your web_modules/ directory immediately.

Importing CSS

import './style.css';

No browser today supports importing a CSS file directly from JS. Instead, you’ll want to use one of the following libraries/solutions:

  1. Recommended! If you’re building a simple app, consider defining your CSS inside your HTML using a <style> block.
  2. Recommended! csz - “Runtime CSS modules with SASS like preprocessing”
  3. @emotion/core - “Simple styling in React.” (Requires Babel)
  4. Most CSS-in-JS libraries will work without a bundler, although some may require extra Babel plugins to work.

Importing Images

import './photo.png';

No browser today supports importing an image directly from JS. Instead, you’ll want to use one of the following libraries/solutions:

  1. Recommended! Keep referencing images by URL. You can put any image URL directly into an <img> src tag: <img src="/img/photo.png">.


Snowpack dramatically speeds up your development time by removing the need for a web application bundler. But you can still use build tools like Babel or TypeScript and get the same speed improvements without the bundler. On every change, your build tool will only need to update a single file, instead of entire bundles.

Below are a collection of guides for using different web frameworks and build tools with Snowpack. If you’d like to add your own, feel free to hit the pencil icon in the top-right to edit our docs.


To use Babel with Snowpack:

  1. Make sure that your entire application lives inside a source directory (ex: src/).
  2. Run Babel to build your src/ application to an output lib/ directory (ex: babel src/ --out-dir lib --watch)
  3. Update your HTML entrypoint to point to your lib/ directory.
  4. Now make changes to your src/ directory, and see them build instantly.
  5. Optional: Check out our Babel plugin for importing packages by name in the guide above.


TypeScript expects imports to be by package name, and won’t be able to understand your “web_modules/” imports by default. You’ll need to follow one of the following guides based on your setup:

With Babel:

While it may sound like overkill, Babel & TypeScript work well together. This article does a good job of explaining how each tackles their own problem better than either could on their own: TypeScript With Babel: A Beautiful Marriage

To use TypeScript with Babel, just use our “Import by Package Name” Babel plugin to rewrite your package name imports at build time. This way, TypeScript will only ever see the package name imports, as expected. See our guide above for more info on connecting this plugin.

Without Babel:

To use TypeScript with Snowpack, you’ll need to set up your tsconfig.json to understand “web_module” import paths. See our annotated tsconfig.json example below:

"compilerOptions": {
// Choose your target based on which browsers you'd like to support.
"target": "es2017",
// Required: Use module="esnext" so that TS won't compile/disallow any ESM syntax.
"module": "esnext",
// Optional, but recommended
"moduleResolution": "node",
// Optional, but recommended
"baseUrl": ".",
// Required: Map "/web_modules/*" imports back to their node_modules/ TS definition files.
"paths": {
"/web_modules/*.js": [
// ...


* NOTE: The Vue package points to the runtime-only distribution by default.
* Unless you are using the Vue CLI, you'll most likely need the full browser build.
* Runtime only: `import Vue from "/web_modules/vue.js"`

import Vue from "/web_modules/vue/dist/vue.esm.browser.js";

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: vue/dist/vue.esm.browser.js. [1.07s]

Psst… are you an expert on Vue? We’d love your help writing a short guide for authoring .vue SFC’s and then compiling them to valid JS!


// File: src/index.js
// Optional, if using HTM instead of JSX and Babel:
import htm from '/web_modules/htm.js';
const html = htm.bind(h);

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: preact, htm. [1.06s]

Relevant guides:


import React, { useState } from '/web_modules/react.js';

Important: React is not yet published with ES Module support, and due to the way it’s written, it’s impossible to install as a dependency (“Error: ‘__moduleExports’ is not exported by node_modules/react/index.js”). However, it is still possible to use React with Snowpack thanks to @sdegutis’s @reactesm project & npm/yarn’s alias feature:

npm install react@npm:@reactesm/react react-dom@npm:@reactesm/react-dom
   yarn add react@npm:@reactesm/react react-dom@npm:@reactesm/react-dom

When installed under the usual react/react-dom alias, Snowpack will install these easier-to-optimize ESM distributions into your web_modules/ directory.


It’s important to keep in mind that JSX isn’t really JavaScript. JSX is a build-time syntax that only your build tooling understands, and that doesn’t run directly in any browser. In any app you work on (bundled or unbundled) you’ll need to use a build tool like Babel or TypeScript to transpile JSX into regular old JavaScript before shipping it to the browser.

To use JSX with Snowpack, you can either:

  1. Use TypeScript with “–jsx” mode enabled. See our “TypeScript” guide for more.
  2. Use Babel with a plugin like @babel/plugin-transform-react-jsx or a framework-specific preset like @babel/preset-react. See our “Babel” guide for more.
  3. Use a JSX-like alternative that can run in the browser. Jason Miller’s htm is a great option (keep reading).


HTM is “a JSX alternative using […] JSX-like syntax in plain JavaScript - no transpiler necessary.” It has first-class support for Preact (built by the same team) but also works with React. happily used HTM + Preact for many months before adding Babel and switching to React + TypeScript.

// File: src/index.js
import htm from '/web_modules/htm.js';
const html = htm.bind(h);

return html`<div id="foo" foo=${40 + 2}>Hello!</div>`

// $ snowpack --include "src/index.js"


lit-html is “an efficient, expressive, extensible HTML templating library for JavaScript.” Similarly to HTM, lit-html uses tagged template literals for JSX-like syntax in the browser without requiring any transpilation.

// File: src/index.js
import { html } from "/web_modules/lit-html.js";
import { until } from "/web_modules/lit-html/directives/until.js";

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: lit-html, lit-html/directives/until.js [0.17s]

Important: There should only ever be one version of lit-html run in the browser. If you are using third-party packages that also depend on lit-html, we strongly recommend adding "lit-html" to your dedupe config to prevent Snowpack from installing multiple versions:

// File: package.json
"snowpack": {
// ...
"dedupe": ["lit-html"]

Important: lit-html directives aren’t exported by the main package. Run Snowpack with the --include flag so that Snowpack can automatically detect these imports in your application. Otherwise, add them each separately to the webDependencies whitelist in your package.json:

// File: package.json
"snowpack": {
"webDependencies": [


LitElement is “a simple base class for creating fast, lightweight web components” built on top of lit-html. Read our lit-html guide for important information.

// File: src/index.js
import { LitElement, html, css } from "/web_modules/lit-element.js";
import { repeat } from "/web_modules/lit-html/directives/repeat.js";

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: lit-html, lit-element [0.25s]


# 1. Build your CSS (add --watch to watch for changes)
sass ./scss:./css
# 2. Load the output css anywhere in your application
<link rel="stylesheet" type="text/css" href="/css/index.css">

SASS is a “mature, stable, and powerful professional grade CSS extension language” that compiles to CSS. Use the SASS CLI to compile your SASS into CSS, and then load that CSS anywhere in your application.

You might also like: csz, run-time CSS modules with support for SASS-like selectors. CSZ runs directly in the browser, so you can skip the SASS build/watch step.

Tailwind CSS

# 1. Build your CSS
npx tailwind build styles.css -o css/tailwind.css
# 2. Load the output file anywhere in your application
<link rel="stylesheet" type="text/css" href="/css/tailwind.css">

Tailwind works as-expected with Snowpack. Just follow the official Tailwind Install Guide. When you get to step #4 (“Process your CSS with Tailwind”) choose the official Tailwind CLI option to generate your CSS. Import that generated CSS file in your HTML application.

Styled Components

// File: src/index.js
import React from "/web_modules/react.js";
import {render} from "/web_modules/react-dom.js";
import styled from "/web_modules/styled-components.js";

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: react, react-dom, styled-components. [1.06s]

Relevant guides:

Material UI

// File: src/index.js
import React from "/web_modules/react.js";
import Button from "/web_modules/@material-ui/core/Button/index.js";

// $ snowpack --include "src/index.js"
// ✔ snowpack installed: @material-ui/core/Button/index.js, react, react-dom. [1.64s]

Relevant guides:


The Workbox CLI integrates well with Snowpack. Run the wizard to bootstrap your first configuration file, and then run workbox generateSW to generate your service worker.

Remember that Workbox expects to be run every time you deploy, as a part of a production “build” process (similar to how Snowpack’s --optimize flag works). If you don’t have one yet, create a "deploy" and/or "build" script in your package.json to automate your production build process.

Migrating an Existing App

How you migrate an existing app to Snowpack depends on which Bundler features/plugins you’re using. If you’re only using the import statement to import other JavaScript files, the process should only take a couple of minutes. If you’re importing CSS, images, or other non-JS content in your application, you’ll need to first get rid of those Webpack-specific imports before migrating away from Webpack.

Assuming you’ve removed all code specific to your bundler, you can use the following rough plan to migrate to Snowpack.

  1. Use Babel to assist in the migration. If you don’t want to use Babel, don’t worry; You can always remove it after migrating.
  2. Follow the Babel guide above to build your existing src/ directory to a new lib/ directory.
  3. Follow the Babel Plugin guide above to add the Snowpack Babel plugin so that your package imports will continue to run as is. Check your output lib/ directory to make sure that dependency imports are being rewritten as expected.
  4. Run your application in the browser! If everything is working, you’re done! Otherwise, use your browser’s dev tools to hunt down any remaining issues in your application code.

Migrating to Webpack/Parcel

Snowpack is designed for zero lock-in. If you ever feel the need to add a traditional application bundler to your stack (for whatever reason!) you can do so in seconds.

Any application built with Snowpack should Just Work™️ when passed through Webpack/Rollup/Parcel. If you are importing packages by full URL (ex: import React from '/web_modules/react.js'), then a simple Find & Replace should help you re-write them to the plain package names (ex: import React from 'react') that bundlers expect.