When developing WordPress themes, do you find yourself reloading your browser every time you save an SCSS file just to check the changes? Gulp’s `watch` command, BrowserSync proxy settings, and complex Webpack configurations… With Vite, you can achieve the experience of seeing styles reflected in your browser the moment you save—without any of those “detours”—in just a few minutes.
In this article, I’ll explain the steps to integrate Vite into an existing WordPress theme (including child themes) and implement CSS HMR (Hot Module Replacement). Since this is based on the configuration of a Cocoon child theme I’m currently using, you can replicate it exactly.
- Why Use Vite for WordPress Development?
- Requirements for Setup
- Directory Structure
- STEP 1: Installing Vite and Updating package.json
- STEP 2: Configuring vite.config.js
- STEP 3: Creating an entry point
- STEP 4: Load Assets in functions.php
- STEP 5: Start the development server and verify operation
- Production Build Configuration
- Dev Script Tips That Make a Difference in Real-World Work
- Example of customizing vite.config.js
- Common Issues and Solutions
- Frequently Asked Questions
- Summary
Why Use Vite for WordPress Development?
In WordPress theme development, PHP is used to generate HTML, while CSS and JavaScript control the front end. When seeking to combine this setup with modern build tools, many articles have traditionally recommended Webpack or Gulp.
Vite takes a fundamentally different approach from these tools. During development, it serves individual files directly using ESM (ES Modules), and only bundles them with Rollup during production builds. In other words, the bundling process does not run during development.
What this means is that even as the number of files increases, the development server does not take longer to start up. With webpack, the server starts only after it has traced dependencies and generated the bundle, but Vite starts the server immediately and transforms only the requested files on demand.
Why HMR Is Blazing Fast
Vite’s HMR (Hot Module Replacement) is, quite literally, lightning-fast. If you edit a single line of SCSS, only the changed portion is updated in the browser without reloading the entire page. To put it in practical terms, the browser updates before you even finish pressing Ctrl+S to save.
With traditional tools like BrowserSync or Gulp LiveReload, the process was: detect file changes → compile → instruct the browser to reload → reload the entire page.Vite replaces the “compile → instruct the browser to reload → reload the page” part of this process with incremental updates delivered via WebSockets. Since a full page reload isn’t necessary, your scroll position and form input state are preserved while only the visual content is updated.
Differences from webpack and Gulp
| Item | Vite | webpack | Gulp BrowserSync |
|---|---|---|---|
| First launch | Hundreds of milliseconds | A few seconds to over ten seconds | A few seconds |
| HMR (when CSS changes) | Instant (milliseconds) | 1–3 seconds | Reload (1–3 seconds) |
| Complexity of configuration files | Simple | Complex (many loaders/plugins) | Moderate |
| ESM support | Native | Via transpilation | Not supported |
| Production build | Rollup (High-Speed) | webpack (Stable) | Manual task design |
Requirements for Setup
The following items are required for this configuration:
- Node.js (v18 or higher recommended)
- A running local WordPress environment (Herd, Local, Docker, wp-env, etc.—anything works)
- Access to the theme directory (child theme recommended)
While methods using wp-env or Docker are commonly introduced, this article uses an approach that works regardless of the type of local environment. Whether you use Herd, DBngin, or Docker, you can follow the same steps for installation.
Directory Structure
After integrating Vite, the child theme directory structure will look like this.
cocoon-child-master/
├── dist/ # ビルド出力先
│ ├── .vite/
│ │ └── manifest.json # アセット対応表
│ ├── main-xxxxx.js
│ └── main-xxxxx.css
├── main.js # エントリーポイント
├── vite.scss # SCSS(main.jsからimport)
├── vite.config.js # Vite設定
├── package.json
├── functions.php # Viteアセットの読み込みロジック
├── style.css # WPテーマ必須ファイル
└── node_modules/
The key point is thatmain.jsserves as the entry point, and from there, SCSS isimport. Vite follows this import chain and treats both CSS and JS as targets for HMR.
STEP 1: Installing Vite and Updating package.json
Execute the following in the theme directory.
cd wp-content/themes/your-child-theme
npm init -y
npm install --save-dev vite vite-plugin-live-reload vite-plugin-mkcert
The roles of the packages to be installed are as follows:
- vite: The core build tool
- vite-plugin-live-reload: A plugin that triggers a full browser reload when PHP files are modified
- vite-plugin-mkcert: A plugin that automatically generates an HTTPS certificate for the local development server
If you are using SCSS/Sass, you will also need to install a Sass compiler.
npm install --save-dev sass
package.jsonThescriptssection should be configured as follows.
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
With this configuration,npm run devwill start the development server,npm run buildand `vite.build` will handle the production build. An improved version of the `dev` script will be introduced later in this article.
STEP 2: Configuring vite.config.js
Here is an overview of the Vite configuration file. I will explain the meaning of each line.
import { defineConfig } from "vite";
import liveReload from "vite-plugin-live-reload";
import mkcert from "vite-plugin-mkcert";
import path from "path";
export default defineConfig({
plugins: [
liveReload(__dirname "/**/*.php"),
mkcert(),
],
root: "",
base: process.env.NODE_ENV === "development"
? "https://localhost:5173/"
: "/wp-content/themes/your-child-theme/dist/",
build: {
outDir: path.resolve(__dirname, "./dist"),
emptyOutDir: true,
manifest: true,
rollupOptions: {
input: path.resolve(__dirname, "main.js"),
},
assetsDir: "",
},
server: {
cors: true,
strictPort: true,
port: 5173,
https: true,
hmr: {
host: "localhost",
},
},
});
Explanation of each configuration item
| Configuration | Value | Role |
|---|---|---|
plugins | liveReload, mkcert | Full reload on PHP changes, HTTPS support |
base | Switch between development and production | Base path for assets. Vite server during development, dist directory in production |
build.manifest | true | Generate manifest.json to allow PHP to resolve filenames after building |
build.rollupOptions.input | main.js | Entry point. Includes SCSS imported from here |
build.assetsDir | “” (empty string) | Output files directly to the `dist` directory (do not create subdirectories) |
server.https | true | Launch an HTTPS development server in conjunction with mkcert |
server.hmr.host | localhost | Explicitly specify the WebSocket connection destination. This may be necessary in Docker environments, etc. |
Reasons for requiring HTTPS
If the WordPress site is running over HTTPS (even locally.testif SSL is enabled on the domain), the Vite server must also use HTTPS; otherwise, the connection will be blocked due to mixed content.vite-plugin-mkcertUsing this will automatically generate a self-signed certificate and start an HTTPS development server.
STEP 3: Creating an entry point
main.jsis the Vite entry point. Import your SCSS here.
// main.js
import './vite.scss';
// ここにJavaScriptのコードを書く
document.addEventListener('DOMContentLoaded', () => {
console.log('Vite HMR is running!');
});
In the SCSS file (vite.scss) contains the child theme’s styles. Thanks to Vite, modifying this SCSS automatically triggers compilation and HMR injection.
STEP 4: Load Assets in functions.php
This is the most important part. We use PHP to switch between loading assets from the Vite development server during development and loading pre-built files via `manifest.json` in the production environment.
Detecting the development environment
First, create a function to check whether the Vite development server is running.
define('VITE_SERVER', 'https://localhost:5173');
define('VITE_ENTRY_POINT', '/main.js');
function is_vite_dev() {
// 本番環境では常にfalseを返す
$host = $_SERVER['HTTP_HOST'] ?? '';
if (strpos($host, 'example.com') !== false) {
return false;
}
// wp-config.phpで定義があれば最優先
if (defined('IS_VITE_DEVELOPMENT')) {
return IS_VITE_DEVELOPMENT;
}
// .hotファイルの有無で判定
return file_exists(get_theme_file_path('/.hot'));
}
The key to this check is the .hot file. When the Vite development server starts,.hotand deleted when it exits (see the dev script described later). This ensures that HMR mode is enabled only when the development server is running.
We’ve also added a guard for the production domain. Even if, by some chance,.hotthe file remains on the production server after deployment, the domain check ensures a safe fallback.
Asset Loading Process
add_action('wp_enqueue_scripts', function () {
if (is_vite_dev()) {
// 開発環境:Vite HMRクライアント エントリーポイント
wp_enqueue_script(
'vite-client',
VITE_SERVER . '/@vite/client',
[], null, true
);
wp_enqueue_script(
'vite-main',
VITE_SERVER . VITE_ENTRY_POINT,
[], null, true
);
} else {
// 本番環境:manifest.jsonからビルド済みファイルを読み込み
$manifest_path = get_theme_file_path('/dist/.vite/manifest.json');
if (!file_exists($manifest_path)) {
$manifest_path = get_theme_file_path('/dist/manifest.json');
}
if (file_exists($manifest_path)) {
$manifest = json_decode(
file_get_contents($manifest_path), true
);
// CSSの読み込み
if (isset($manifest['main.js']['css'])) {
foreach ($manifest['main.js']['css'] as $css_file) {
wp_enqueue_style(
'vite-style',
get_theme_file_uri('/dist/' . $css_file)
);
}
}
// JSの読み込み
if (isset($manifest['main.js']['file'])) {
wp_enqueue_script(
'vite-main',
get_theme_file_uri('/dist/' . $manifest['main.js']['file']),
[], null, true
);
}
}
}
}, 100);
In the development environment,@vite/client(the WebSocket client for HMR) and the entry point.@vite/clienthandles communication with the Vite server and injects CSS when changes occur.
Add `type=”module”` to the `script` tag
Since Vite scripts are served in ESM format,the tag requirestype="module"attribute is required. Add it using thescript_loader_tag.
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if (in_array($handle, ['vite-client', 'vite-main'])) {
return '';
}
return $tag;
}, 10, 3);
STEP 5: Start the development server and verify operation
Once all settings are complete, start the development server.
npm run dev
If the Vite logo andhttps://localhost:5173appears in the terminal, it means the setup was successful. When you open the WordPress site in a browser in this state, assets will be served via the Vite development server.
Try changing the color in an SCSS file and saving it. The color in the browser will change instantly without having to reload the page. This is the power of HMR.
When modifying PHP files, a full reload
vite-plugin-live-reloadmonitors changes to PHP files.functions.phpEditing PHP or template files triggers an automatic full reload in the browser. Since the system automatically switches between HMR and full reload, developers don’t need to worry about it.
Production Build Configuration
Once development is complete, build the production files.
npm run build
This willdist/bundled CSS and JS files, along with a `manifest.json` file, are output to the directory.
dist/
├── .vite/
│ └── manifest.json
├── main-CEBPsstE.js
└── main-CmEZvCsu.css
Hashes are appended to filenames for cache busting. Since the hash changes whenever the file content changes, this prevents the issue of old CSS being applied due to browser caching.
Since `manifest.json` is loaded via WordPress’s `functions.php`, no changes to the PHP code are required even if the hash changes. Simply building the project automatically ensures the latest files are referenced.
Dev Script Tips That Make a Difference in Real-World Work
While the basic setup described so far is fully functional, in real-world production environments, accidents like "forgetting to run the build after shutting down the development server, resulting in outdated CSS appearing on the production site" tend to occur.
To prevent this, I’ll introduce a dev script that automatically runs a build when the development server shuts down.
{
"scripts": {
"dev": "bash -lc 'touch .hot; trap "rm -f .hot; npm run build; trap - EXIT" INT TERM EXIT; vite'",
"build": "vite build"
}
}
Here’s what this script does:
touch .hot— Creates a .hot file. This serves as an indicator that functions.php is in HMR mode.trap "rm -f .hot; npm run build; ..." INT TERM EXIT— Sets up a trap for Ctrl+C or when the terminal is closedvite— Starts the development server- When the development server shuts down (Ctrl+C) → Delete the .hot file → Run the build automatically
With this setup,npm run devyou can start development and simply stop with Ctrl+C, ensuring the latest build files are always preserved. Since this eliminates the risk of forgetting to build, I strongly recommend this method.
Example of customizing vite.config.js
High-speed CSS transformation with LightningCSS
In Vite, by replacing the CSS transformer with LightningCSS, you can perform automatic vendor prefixing and nested syntax conversion at native speeds.
// vite.config.js に追加
css: {
transformer: "lightningcss",
},
npm install --save-dev lightningcss
Using with Tailwind CSS
Vite also works well with Tailwind CSS. In addition to the standard Tailwind setup procedure, simply import a CSS file containing the@tailwindin `main.js`.
Multiple Entry Points
If you want to use different scripts for the frontend and the admin panel,rollupOptions.inputset it to object format.
rollupOptions: {
input: {
main: path.resolve(__dirname, "main.js"),
admin: path.resolve(__dirname, "admin.js"),
},
},
Common Issues and Solutions
HMR isn't working
- Check the browser console to see if there are any WebSocket connection errors
- HTTPS/HTTP Mismatch: If WordPress is running on HTTPS, configure the Vite server to use HTTPS via mkcert
- Existence of .hot files:
is_vite_dev()Check if it is returning true correctly - CORS error:
vite.config.js'sserver.corsistrueis set
CSS cannot be loaded after building
dist/.vite/manifest.json(Vite 5 and later) ordist/manifest.jsonCheck if it existsfunctions.phpCheck ifis_vite_dev()Check if it returns false (to ensure no .hot files remain)baseCheck if the production path matches the theme directory
CORS error occurs
Check if the WordPress domain (e.g.,my-site.test) and the Vite server (localhost:5173) have different origins, so they are blocked by CORS by default.server.cors: truePlease configure
.gitignore configuration
The following files and directories should be excluded from Git version control.
node_modules/
.hot
dist/Whether to include these in Git depends on your team’s policy. It is common practice to exclude them for CI builds and include them for manual deployments.
Frequently Asked Questions
- Can I use this with wp-env or Docker?
Yes, it works. However, to access the Vite server on localhost from WordPress inside Docker,
server.hmr.host. Native environments like Herd tend to be simpler to configure.- Can it coexist with jQuery?
Yes, it is possible. Since the jQuery that WordPress loads by default is outside of Vite’s management scope, they can coexist as-is. If you want to manage it with Vite,
import $ from 'jquery', you can import it via , but be careful not to cause a double load with WordPress’s jQuery.- Can I use React or Vue.js?
Since Vite is also the official build tool for React and Vue.js, you can certainly use them. Vite is also a strong option when using React for WordPress custom block development. However, since its configuration system differs from WordPress’s standard `@wordpress/scripts`, you’ll need to use them appropriately when developing plugins.
- Can I migrate smoothly from an existing Gulp environment?
If your main use cases for Gulp are compiling SCSS/Sass and browser reloading, migrating to Vite is relatively straightforward. Simply replace your `gulpfile.js` configuration with `vite.config.js` and update your setup to import SCSS from `main.js`. Even if you’re using the PostCSS plugin, Vite supports PostCSS natively.
- Is Node.js required on the production server?
No. Vite is strictly a development and build tool. All that’s needed for the production environment
dist/the pre-built files in the directory; there is no need to install Node.js.
Summary
Let’s review the steps for integrating Vite into a WordPress theme.
- Install Vite and related plugins in the theme directory
- Define the entry point, generate the manifest, and configure HTTPS in `vite.config.js`
- Import SCSS from `main.js` to create the entry point
- Implement development/production switching logic and asset loading in `functions.php`
- Launch the development server with `npm run dev` and experience lightning-fast HMR
Once you experience SCSS changes reflecting in the browser the moment you save them, you’ll never want to go back to waiting for Gulp or BrowserSync to reload. With minimal configuration and few files, and easy integration into existing themes, this is a must-try for anyone looking to modernize their WordPress frontend development.
If you want to start by setting up a local environment, check out “Build a WordPress Local Environment in 5 Minutes with Herd and DBngin [Mac].” It explains how to set up a native PHP environment that works well with Vite.

Comment