Hot reload & dev container¶
TL;DR¶
- Edit a file anywhere under
apps/web/app/,apps/web/components/,apps/web/public/, orapps/web/next.config.ts. - Save.
- The browser at
https://dev.fruitplug.co.uk/auto-updates within 1–2 seconds.
No container restart needed. No build step needed.
How it works¶
flowchart LR
You([You edit page.tsx]) --> Win[Windows FS<br/>C:\...\apps\web\app\page.tsx]
Win -.bind mount.-> Container[fruitplug-web-dev<br/>/repo/apps/web/app/page.tsx]
Container -->|webpack polling<br/>800ms interval| NextDev[next dev --webpack]
NextDev -->|WS HMR| Caddy[Caddy]
Caddy -->|WS| Browser[Your browser]
Why webpack, not Turbopack¶
Next.js 16 defaults to Turbopack. Turbopack's file watcher uses Rust's notify crate, which relies on inotify events. On Windows, bind-mounted files into a Linux container don't propagate inotify events through WSL2. Result: edits happen on disk but the watcher never fires.
Webpack's watcher supports explicit polling, which works fine through the bind mount:
// apps/web/next.config.ts
webpack: (config, { dev }) => {
if (dev) {
config.watchOptions = {
poll: 800, // poll every 800ms
aggregateTimeout: 200, // batch changes within 200ms
ignored: ["**/node_modules/**", "**/.next/**", "**/.git/**"],
};
}
return config;
}
And the Docker command:
The dev container¶
infra/dev.Dockerfile installs dependencies only. Source is bind-mounted at runtime so there's no image rebuild for source changes.
Start manually:
docker run -d \
--name fruitplug-web-dev \
--restart unless-stopped \
-p 127.0.0.1:3030:3000 \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/app:/repo/apps/web/app" \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/components:/repo/apps/web/components" \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/public:/repo/apps/web/public" \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/next.config.ts:/repo/apps/web/next.config.ts" \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/tsconfig.json:/repo/apps/web/tsconfig.json" \
-v "/c/Users/User/desktop/fruitplug/fruitplug-web/apps/web/postcss.config.mjs:/repo/apps/web/postcss.config.mjs" \
fruitplug-web:dev
Or rebuild the image (when package.json changes):
Caddy is transparent to HMR¶
Next.js dev uses WebSockets for HMR on /_next/webpack-hmr. Caddy's reverse_proxy handles WebSocket upgrade automatically — no special config needed.
The one gotcha: Next 16 dev blocks cross-origin requests to dev resources by default. We allow dev.fruitplug.co.uk in next.config.ts:
What doesn't hot-reload¶
package.jsonchanges → rebuild the dev image (docker build -f infra/dev.Dockerfile -t fruitplug-web:dev .) then restart the containernext.config.tschanges to non-webpack sections (e.g.images.remotePatterns) → restart the container (docker restart fruitplug-web-dev)- Environment variables → restart
Troubleshooting¶
Page doesn't update after save.
Check the container logs:
If you don't see a "compiled" line, the poll interval may not have fired yet. Wait 1–2s and request the page again with a cache-busting query string:
"Cross-origin request blocked" warning.
Add the offending origin to allowedDevOrigins in next.config.ts and restart the container.
File shows updated inside container but browser still shows old.
Rule out browser cache: hard refresh (Ctrl+Shift+R) or open DevTools → Network → "Disable cache".