Vite CORS Configuration Fix
April 26, 2025 · AI Conversations
Access to script at 'http://localhost:5173/src/main.jsx' from origin 'https://wprock.ddev.site' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
**What's happening:**
You're running Vite dev server (`localhost:5173`) but trying to access it from another domain (`https://wprock.ddev.site`).
Browsers block this by default unless CORS headers (`Access-Control-Allow-Origin`) are properly sent.
—
### How to fix it:
✅ You **must tell Vite to allow CORS from your `.ddev.site` domain**.
In your **Vite config (`vite.config.js`)**, add this:
export default {
server: {
host: '0.0.0.0', // allow external access
port: 5173,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*', // Or more strictly: 'https://wprock.ddev.site'
},
strictPort: true,
hmr: {
host: 'wprock.ddev.site',
protocol: 'wss', // if you're using https on ddev
},
},
}
👉 **Important notes**:
– Use `wss` if your DDEV site is HTTPS (which it is: `https://wprock.ddev.site`).
– Setting `host: '0.0.0.0'` makes Vite accessible from the outside (your DDEV domain can see it).
– `headers['Access-Control-Allow-Origin']` allows CORS.
– `strictPort: true` is optional but helps prevent random port changes.
—
### After editing `vite.config.js`:
1. Restart your Vite server:
yarn dev
# or
npm run dev
2. Clear browser cache if needed (especially if Chrome cached the CORS block).
—
### Extra tip:
If DDEV is HTTPS but Vite is only HTTP (`http://localhost:5173`), it may also give **"mixed content"** errors later (after fixing CORS).
In that case, you might also need to:
– **Trust certificates** or
– **Use a self-signed HTTPS dev server for Vite** (if needed, I can show you that too).
—
Would you like me to also give you a fully ready **example vite.config.js** based on your DDEV + React + WordPress setup? 🚀
(Only if you want to speed things up.)
import react from '@vitejs/plugin-react'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export default defineConfig({
base: '', // for WordPress theme builds
plugins: [react()],
build: {
outDir: 'dist',
assetsDir: 'assets',
manifest: true,
emptyOutDir: true,
rollupOptions: {
input: path.resolve(__dirname, 'src/main.jsx'),
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
host: '0.0.0.0', // expose to all interfaces
port: 5173,
strictPort: true,
origin: 'http://localhost:5173',
cors: true,
headers: {
'Access-Control-Allow-Origin': '*', // or use your exact domain
},
hmr: {
protocol: 'ws',
host: 'localhost',
port: 5173,
},
},
})
You’re **very close**, but **one small thing** is causing your CORS issue when accessing from `https://wprock.ddev.site`:
—
**Problem:**
hmr: {
- protocol: 'ws',
- host: 'localhost',
- port: 5173,
},
You have `host: 'localhost'`, but **your browser page is running on `wprock.ddev.site`**, not on localhost!
That's why WebSocket and CORS both fail.
—
✅ **Correct HMR settings for your setup:**
hmr: {
+ protocol: 'wss', // if your ddev site is https
+ host: 'wprock.ddev.site',
+ port: 5173,
},
✔️ And keep `headers: { 'Access-Control-Allow-Origin': '*' }` for CORS.
✔️ And `host: '0.0.0.0'` is already correct.
—
### Full Corrected Vite Config for DDEV + WordPress (HTTPS)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export default defineConfig({
base: '',
plugins: [react()],
build: {
outDir: 'dist',
assetsDir: 'assets',
manifest: true,
emptyOutDir: true,
rollupOptions: {
input: path.resolve(__dirname, 'src/main.jsx'),
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
origin: 'https://wprock.ddev.site', // optional but better
hmr: {
protocol: 'wss', // HTTPS sites need secure websocket
host: 'wprock.ddev.site',
port: 5173,
},
},
})
—
### After this:
– Restart Vite (`yarn dev` or `npm run dev`).
– Visit your `https://wprock.ddev.site` site — now it should load Vite scripts fine without CORS issues 🚀.
—
### Why change `ws` → `wss`?
Because `wprock.ddev.site` is HTTPS.
If Vite HMR connects with an insecure `ws://`, browser **blocks it** for security reasons.
—
Would you also like me to show you a simple **proxy setup** so you don't have to open port 5173 separately next time? (even cleaner setup 🚀)
(Let me know if you want!)
Yes, you are **using React + Vite as a WordPress theme** — **inside DDEV** — and not running it separately like a typical Vite app.
That's even more reason why **fixing the Vite dev server settings properly** is important.
—
### ✅ Let me quickly break it down for your exact case:
**Right now your flow is:**
| Environment | Purpose |
|————————–|———————————————-|
| Vite dev server (localhost:5173) | Serves React assets live (hot reload) |
| DDEV WordPress (https://wprock.ddev.site) | Main site loads WordPress + theme (React assets injected) |
When you build for production (`yarn build`), Vite will generate `/dist` files and WordPress will serve them normally — **no CORS issues** at all.
But during **development**,
– WordPress theme template (PHP) tries to load assets from `http://localhost:5173/src/main.jsx`.
– Browser **blocks it** because of CORS + protocol mismatch (`https` vs `http`).
—
### 🔥 Therefore, **for WordPress theme using Vite dev server**, the correct setup is:
**In `vite.config.js`:**
server: {
host: '0.0.0.0', // Allow DDEV containers to access
port: 5173,
strictPort: true,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*', // or just 'https://wprock.ddev.site'
},
origin: 'https://wprock.ddev.site', // Important: match your WordPress site URL
hmr: {
protocol: 'wss', // Secure WebSocket for HTTPS WordPress
host: 'wprock.ddev.site', // Use the DDEV domain
port: 5173,
},
},
✅ This way WordPress can **pull Vite scripts directly during dev** without CORS failure.
✅ Vite HMR (`Hot Module Reload`) also works correctly (even inside WordPress).
—
### 🛠️ Also make sure in WordPress PHP side:
When enqueuing Vite assets in your theme (like `wp_enqueue_script`),
you dynamically check if it's **development** or **production**.
Example PHP check:
<?php
// functions.php or similar
$is_dev = str_contains($_SERVER['HTTP_HOST'], '.ddev.site') && file_exists(get_theme_file_path('vite.config.js'));
if ($is_dev) {
// Load from Vite dev server
wp_enqueue_script('theme-main', 'https://wprock.ddev.site:5173/src/main.jsx', array(), null, true);
} else {
// Load from built /dist folder
$manifest = json_decode(file_get_contents(get_theme_file_path('dist/manifest.json')), true);
$main = $manifest['src/main.jsx']['file'];
wp_enqueue_script('theme-main', get_template_directory_uri() . '/dist/' . $main, array(), null, true);
}
?>
This way:
– In development, load live from Vite (`localhost:5173` via DDEV URL).
– In production, load from `/dist` built files.
—
### 🔥 To summarize:
| Step | Status |
|————————-|—————-|
| Fix Vite CORS + HMR ✅ | Yes |
| Make Vite accessible inside DDEV ✅ | Yes |
| WordPress dynamically loads dev vs prod assets ✅ | Yes |
| Build production bundle (`yarn build`) later ✅ | Yes |
—
### 🚀 **Final Result** after this:
– `npm run dev` → automatic hot reloading inside WordPress theme
– `yarn build` → production-ready `/dist` assets for real WordPress usage
– No CORS issues
– No HMR failures
—
Would you like me to also give you a sample **WordPress + React Vite theme structure**? (like what goes inside `src/`, `dist/`, `functions.php`, etc.)
If yes, I can prepare a quick blueprint for you! 🔥
Would be very helpful if you're planning a full production move later.
Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong.
at Home.jsx:9:5
You have **fixed the CORS**, but now you’re seeing this:
Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong.
at Home.jsx:9:5
—
✅ **This error means:**
Vite’s React plugin **(@vitejs/plugin-react)** needs the Vite "preamble" (`@vite/client`) to be loaded **before** your React app.
Preamble = Vite’s internal HMR client, fast refresh, etc.
But right now, **WordPress page is trying to load only your `main.jsx`** — **without** loading the Vite internal runtime (`@vite/client`).
That's why React plugin **crashes**.
—
# 🎯 How to fix:
You need to manually load **`@vite/client`** before your `main.jsx` when running in development mode (not needed for production).
✅ In your WordPress theme, **enqueue `@vite/client` first**, then your `main.jsx`.
—
### 📄 Correct WordPress enqueue setup for Vite:
In your `functions.php` (or wherever you enqueue scripts):
<?php
function theme_enqueue_scripts() {
$is_dev = str_contains($_SERVER['HTTP_HOST'], '.ddev.site') && file_exists(get_theme_file_path('vite.config.js'));
if ($is_dev) {
// Dev mode: load Vite client and entry point separately
// 1. Vite client (for HMR and React refresh)
wp_enqueue_script('vite-client', 'https://wprock.ddev.site:5173/@vite/client', array(), null);
// 2. Your main app entry
wp_enqueue_script('theme-main', 'https://wprock.ddev.site:5173/src/main.jsx', array('vite-client'), null);
} else {
// Prod mode: load bundled files from /dist
$manifest = json_decode(file_get_contents(get_theme_file_path('dist/manifest.json')), true);
$main = $manifest['src/main.jsx']['file'];
wp_enqueue_script('theme-main', get_template_directory_uri() . '/dist/' . $main, array(), null, true);
}
}
add_action('wp_enqueue_scripts', 'theme_enqueue_scripts');
?>
—
# 🛠 Why?
| Script | Purpose |
|———————-|———————————————-|
| `@vite/client` | Internal Vite HMR runtime, hot refresh |
| `src/main.jsx` | Your React app entry point |
If you skip `@vite/client`, Vite can’t inject React Fast Refresh or HMR → React plugin crashes.
—
# 🧠 So what happened now:
– You **fixed** CORS and HMR config ✅
– You **missed** loading Vite preamble `@vite/client` ✅ (now fixing)
– Now **React + Vite + WordPress theme will work properly** ✅
—
# ⚡ Quick Checklist (Final):
– [x] Vite running with `npm run dev`
– [x] `@vite/client` loaded first
– [x] `src/main.jsx` loaded after
– [x] Browser loads WordPress normally without JS crash
– [x] HMR (Hot Module Reload) works inside WordPress 🎉
—
Would you like me to show you a quick **cleaner version** where all this is wrapped inside a PHP helper (`vite_asset()` function)?
(Lot of people using Vite with WordPress use this to avoid repeating code.) 🚀
Should I?
<script>
</script>
<script>
</script>
EOT;
} else {
// Production: load from built assets
$manifest_path = get_template_directory() . '/dist/.vite/manifest.json';
if (!file_exists($manifest_path)) return;
$manifest = json_decode(file_get_contents($manifest_path), true);
if (!isset($manifest[$entry])) return;
$main = $manifest[$entry];
// CSS
if (!empty($main['css'])) {
foreach ($main['css'] as $css_file) {
wp_enqueue_style(
'vite-style',
get_template_directory_uri() . '/dist/assets/' . basename($css_file),
[],
null
);
}
}
// JS with type="module"
add_filter('script_loader_tag', function ($tag, $handle) {
if ($handle === 'vite-js') {
$tag = str_replace('
Your code is **90% correct** but **needs a few small important fixes** for a perfect WordPress + Vite + React theme integration.
Let’s go through it carefully:
—
# 🛠 Problems in your current code:
### 1. **Environment check**
$is_dev = wp_get_environment_type() === 'development';
✅ Good, but **`wp_get_environment_type()`** depends on how your WordPress is configured (`wp-config.php`).
If you didn't define it, WordPress treats it as "production" even on DDEV.
👉 Better: Detect based on domain (`.ddev.site`) + fallback.
—
### 2. **Wrong manifest path**
$manifest_path = get_template_directory() . '/dist/.vite/manifest.json';
⛔ **Wrong:**
Vite generates `dist/manifest.json` (not `.vite/manifest.json`).
✅ Should be:
$manifest_path = get_template_directory() . '/dist/manifest.json';
—
### 3. **Incorrect asset URL generation**
get_template_directory_uri() . '/dist/assets/' . basename($css_file)
⛔ **Wrong:**
The `basename()` removes folders! But Vite manifest gives **full relative paths** like:
{
"src/main.jsx": {
"file": "assets/main-xyz123.js",
"css": ["assets/main-xyz123.css"]
}
}
You should not `basename()`.
✅ Correct is:
get_template_directory_uri() . '/dist/' . $css_file
—
### 4. **Not scoped properly**
Instead of direct `echo`, better to **hook into `wp_head`** safely or echo at a specific action hook to avoid output ordering issues.
—
# ✨ Here’s your fully corrected version:
<?php
function wp_react_vite_assets() {
$dev_server = 'https://wprock.ddev.site:5173';
$entry = 'src/main.jsx';
// Smarter dev check: DDEV detection
$is_dev = (strpos($_SERVER['HTTP_HOST'], '.ddev.site') !== false);
if ($is_dev) {
// Dev mode: load Vite from dev server
echo <<<EOT
<script type="module" crossorigin src="$dev_server/@vite/client"></script>
<script type="module" crossorigin src="$dev_server/$entry"></script>
EOT;
} else {
// Production mode: load compiled assets
$manifest_path = get_template_directory() . '/dist/manifest.json';
if (!file_exists($manifest_path)) return;
$manifest = json_decode(file_get_contents($manifest_path), true);
if (!isset($manifest[$entry])) return;
$main = $manifest[$entry];
// Load CSS if exists
if (!empty($main['css'])) {
foreach ($main['css'] as $css_file) {
wp_enqueue_style(
'vite-style',
get_template_directory_uri() . '/dist/' . $css_file,
[],
null
);
}
}
// Force module type for JS
add_filter('script_loader_tag', function ($tag, $handle) {
if ($handle === 'vite-js') {
$tag = str_replace('<script ', '<script type="module" ', $tag);
}
return $tag;
}, 10, 2);
wp_enqueue_script(
'vite-js',
get_template_directory_uri() . '/dist/' . $main['file'],
[],
null,
true
);
}
}
add_action('wp_head', 'wp_react_vite_assets'); // load in <head> early
—
# ✅ Why these fixes are important:
| Problem | Correct fix |
|————————————|——————————————–|
| `.vite/manifest.json` wrong path | Use `dist/manifest.json` |
| `basename()` wrong asset URL | Use full path from manifest |
| Possible production environment mismatch | Detect `.ddev.site` |
| Output order | Hook into `wp_head` instead of direct echo randomly |
—
# 🚀 Now full flow:
| Environment | Action |
|——————-|——————————————-|
| Dev (`.ddev.site`) | Load from `https://wprock.ddev.site:5173` live |
| Prod | Load from `/dist/manifest.json` built files |
No CORS error ✅
No preamble missing error ✅
Perfect React/Vite HMR inside WordPress ✅
—
Would you also like me to give a **small helper `vite_asset()` function** so you can cleanly print assets in PHP like:
echo vite_asset('src/main.jsx');
?
(That’s how modern WordPress+Vite setups like Bedrock/Sage handle it — very clean.) 🚀
Should I prepare that too? 🔥
You’re saying:
> "All above code is fine — but https://wprock.ddev.site/ **shows a blank page** (nothing displaying)."
✅ Your WordPress + React + Vite theme setup is basically correct.
✅ No CORS error now.
✅ No Vite preamble error now.
—
# 🎯 **What this actually means:**
If **https://wprock.ddev.site** is showing a **blank page**:
– WordPress **PHP is working** (no fatal error → otherwise you'd see 500 error).
– But your **React app is NOT rendering anything** into the page.
—
### 🧠 Likely Causes:
| Cause | Explanation |
|:—–|:————|
| React App is not properly mounting | Your `src/main.jsx` is probably NOT calling `ReactDOM.createRoot` properly into the correct `div` |
| Missing `#app` div | WordPress template may be missing a “ for React to render inside |
| Dev server assets loaded but nothing rendered | Good sign — just a missing DOM hook |
—
# 🔥 Checklist to fix blank screen
## 1. Make sure your WordPress `page.php`, `index.php`, `front-page.php` (wherever) has:
<div id="app"></div>
✅ This `div` must exist **before** React tries to mount.
Otherwise React has no DOM element to hook into.
—
## 2. Your `src/main.jsx` **must** look like:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
const root = document.getElementById('app'); // 👈 Must match div id="app"
if (root) {
ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} else {
console.error('No #app div found!');
}
✅ This way, React **mounts** inside “ properly.
—
# 🧹 To summarize:
| What you should check | How |
|————————|—-|
| Theme template | Has “ |
| Vite dev server running? | `npm run dev` must be running |
| ReactDOM code | Using `document.getElementById('app')` |
| Browser console | No JS error like `Cannot read property 'createRoot' of null` |
—
# 📋 Quick Example:
**WordPress theme template (`page.php`)**:
<?php get_header(); ?>
<div id="app"></div>
<?php get_footer(); ?>
**React `main.jsx`**:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
const root = document.getElementById('app');
if (root) {
ReactDOM.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
—
# 🚑 If still blank after this:
– Open browser console → Any errors?
– Check **Network Tab** → Is `main.jsx` loaded successfully?
– Check **Sources Tab** → Is React code injected properly?
—
# ⚡ Final note:
✅ **If “ is missing, React has nowhere to render → page stays empty.**
✅ **If Vite is not running, page stays empty or stuck.**
✅ **If ReactDOM createRoot points to wrong ID, blank screen.**
—
Would you like me to show you a **very clean minimal working WordPress + Vite + React example** repo structure? (like 5-minute setup) 🚀
If yes, I can write it for you!
It'll remove all guesswork.
Do you want it? 🎯
at Home.jsx:9:5
Let's **pause and properly fix** what you are facing.
—
You again have this **same error**:
> `Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong.`
and it is happening **in `Home.jsx` at line 9**.
—
✅ This error **100%** means:
**Vite React Plugin (`@vitejs/plugin-react`) expects that `@vite/client` is loaded before your React app.**
But **your WordPress page** is **only loading your `main.jsx`**, and **NOT loading `@vite/client` first**.
—
# 🎯 So, the actual problem is simple:
– You **need** to load two things from Vite dev server:
– `@vite/client`
– your `src/main.jsx`
– And `@vite/client` **must be loaded first** before `main.jsx`.
—
# 🔥 Very important:
You are using this PHP right now:
echo <<<EOT
<script type="module" crossorigin src="$dev_server/@vite/client"></script>
<script type="module" crossorigin src="$dev_server/$entry"></script>
EOT;
✅ This looks **correct**.
But now the critical thing is:
– When you visit **https://wprock.ddev.site**,
**do you see `@vite/client` loaded** in browser network tab?
If `@vite/client` fails to load (for example: 404 error),
then Vite React Refresh breaks and you get **"can't detect preamble"** error.
—
# 🧠 Let's fix step-by-step:
## Step 1: Make sure `@vite/client` is accessible
In browser dev tools:
– Open **Network Tab**.
– Refresh the page.
– See if there is **a request for `https://wprock.ddev.site:5173/@vite/client`**.
– Is it **successful** (200) or **error** (404, failed)?
If `@vite/client` is not reachable, then React Fast Refresh crashes.
—
## Step 2: Fix vite.config.js if needed
In your `vite.config.js`, **make sure**:
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
cors: true,
origin: 'https://wprock.ddev.site',
headers: {
'Access-Control-Allow-Origin': '*',
},
hmr: {
protocol: 'wss', // because wprock.ddev.site is https
host: 'wprock.ddev.site',
port: 5173,
},
},
✅ Ensure `host: '0.0.0.0'`, not `localhost` inside Vite server.
—
## Step 3: Check browser console
When you refresh `https://wprock.ddev.site`,
– Are there any **404 errors**?
– Especially for `@vite/client`?
Example bad error:
GET https://wprock.ddev.site:5173/@vite/client net::ERR_CONNECTION_REFUSED
If you see this, it means **WordPress cannot reach your Vite dev server**.
—
# 📋 Full checklist:
| Check | How |
|:——|:—|
| `@vite/client` loaded first | Yes (Network tab) |
| No CORS error | Yes |
| Vite dev server running | `npm run dev` must be active |
| Correct host in vite.config.js | `0.0.0.0`, wprock.ddev.site |
| HTTPS or WSS consistent | Both must match (not HTTP HTTPS mixed) |
—
# 🛠 What you should do immediately:
1. Open Chrome DevTools → Network Tab → search for `client`
2. Check if `https://wprock.ddev.site:5173/@vite/client` is loaded ✅ or failed ❌
3. If failed → your Vite server is not exposing correctly
4. Fix `vite.config.js` server settings
5. Restart Vite server (`npm run dev` again)
—
# ⚡ Quick Tip:
In some DDEV environments, you might need to **port-forward 5173** if your Vite server inside Docker is isolated.
Example command:
ddev proxy --port 5173
Or set a custom proxy in `.ddev/config.yaml`.
—
# 🚑 In simple words:
**This is not a React problem.**
**This is Vite client injection problem.**
Once `@vite/client` loads properly → your error `can't detect preamble` will disappear.
—
Would you like me to give you a **simple way to test if Vite is properly reachable from WordPress** before even React loads? (Like a quick Vite ping test 🔥)
Should I? 🚀
It will make debugging much faster.