diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 36a4feb7e05f4c7bb4d53e76e80dbe8ceb9d7714..4689543d5082809686e119727721659206292d8e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -99,9 +99,17 @@ If your language is not listed in Crowdin, please open an issue.
 ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
 
 ## Development
-During development, it is useful to use the `yarn dev` command.
-This command monitors the server-side and client-side source files and automatically builds them if they are modified.
-In addition, it will also automatically start the Misskey server process.
+During development, it is useful to use the 
+
+```
+yarn dev
+```
+
+command.
+
+- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
+- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
+- Service Worker is watched by esbuild.
 
 ## Testing
 - Test codes are located in [`/test`](/test).
diff --git a/package.json b/package.json
index 01aabc099b02a22c5d7ad620ff891c863b77a801..1090c8eb1c00bb4eaf8dd91840bfcfa01f125fca 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,8 @@
 	],
 	"private": true,
 	"scripts": {
-		"build": "yarn workspaces foreach run build && yarn run gulp",
+		"build-pre": "node ./scripts/build-pre.js",
+		"build": "yarn build-pre && yarn workspaces foreach run build && yarn run gulp",
 		"start": "cd packages/backend && node ./built/boot/index.js",
 		"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/index.js",
 		"init": "yarn migrate",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index ae39fd591c05c5d9f6f6e164c712c584847f7035..b4cf30d359d138b632d9bda3e3856b1c1c744bde 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -28,6 +28,7 @@
 		"@fastify/accepts": "4.1.0",
 		"@fastify/cookie": "^8.3.0",
 		"@fastify/cors": "8.2.0",
+		"@fastify/http-proxy": "^8.4.0",
 		"@fastify/multipart": "7.3.0",
 		"@fastify/static": "6.6.0",
 		"@fastify/view": "7.3.0",
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 11d8db5c04e09003594c3f7bcd6c4fee8cfad9f1..025d7acdeb00e4c17129727c7fc7e9a4d4585541 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -91,6 +91,7 @@ export type Mixin = {
 	driveUrl: string;
 	userAgent: string;
 	clientEntry: string;
+	clientManifestExists: boolean;
 };
 
 export type Config = Source & Mixin;
@@ -112,7 +113,10 @@ const path = process.env.NODE_ENV === 'test'
 
 export function loadConfig() {
 	const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
-	const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_client_dist_/manifest.json`, 'utf-8'));
+	const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json')
+	const clientManifest = clientManifestExists ?
+		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
+		: { 'src/init.ts': { file: 'src/init.ts' } };
 	const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
 
 	const mixin = {} as Mixin;
@@ -134,6 +138,7 @@ export function loadConfig() {
 	mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
 	mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
 	mixin.clientEntry = clientManifest['src/init.ts'];
+	mixin.clientManifestExists = clientManifestExists;
 
 	if (!config.redis.prefix) config.redis.prefix = mixin.host;
 
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 58452ae826f492ee1da02eb08778fee37930e835..c537d9a369e94f28bef685bbd81795e8646fd8ff 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -12,6 +12,7 @@ import { In, IsNull } from 'typeorm';
 import fastifyStatic from '@fastify/static';
 import fastifyView from '@fastify/view';
 import fastifyCookie from '@fastify/cookie';
+import fastifyProxy from '@fastify/http-proxy';
 import type { Config } from '@/config.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
 import { DI } from '@/di-symbols.js';
@@ -39,6 +40,7 @@ const staticAssets = `${_dirname}/../../../assets/`;
 const clientAssets = `${_dirname}/../../../../client/assets/`;
 const assets = `${_dirname}/../../../../../built/_client_dist_/`;
 const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
+const viteOut = `${_dirname}/../../../../../built/_vite_/`;
 
 @Injectable()
 export class ClientServerService {
@@ -151,9 +153,6 @@ export class ClientServerService {
 			},
 			defaultContext: {
 				version: this.config.version,
-				getClientEntry: () => process.env.NODE_ENV === 'production' ?
-					this.config.clientEntry :
-					JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'],
 				config: this.config,
 			},
 		});
@@ -164,6 +163,23 @@ export class ClientServerService {
 			done();
 		});
 
+		//#region vite assets
+		if (this.config.clientManifestExists) {
+			fastify.register(fastifyStatic, {
+				root: viteOut,
+				prefix: '/vite/',
+				maxAge: ms('30 days'),
+				decorateReply: false,
+			});
+		} else {
+			fastify.register(fastifyProxy, {
+				upstream: 'http://localhost:5173', // TODO: port configuration
+				prefix: '/vite',
+				rewritePrefix: '/vite',
+			});
+		}
+		//#endregion
+
 		//#region static assets
 
 		fastify.register(fastifyStatic, {
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index ffd8b8941c069bf108647c554f025521cf1808bc..86df3308eca1e1618c16d1f1b8cd73f05e147864 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -57,7 +57,7 @@
 
 	//#region Script
 	function importAppScript() {
-		import(`/assets/${CLIENT_ENTRY}`)
+		import(`/vite/${CLIENT_ENTRY}`)
 			.catch(async e => {
 				await checkUpdate();
 				console.error(e);
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 1b3ac82d2e7abd54b3fcd4ef6fab8422615e71d9..0c3c5c9b7ec3444fb57c93c1127b8def02874136 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -1,7 +1,7 @@
 block vars
 
 block loadClientEntry
-	- const clientEntry = getClientEntry();
+	- const clientEntry = config.clientEntry;
 
 doctype html
 
@@ -35,11 +35,14 @@ html
 		link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
 		link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
 		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.css')
-		link(rel='modulepreload' href=`/assets/${clientEntry.file}`)
+		link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
+
+		if !config.clientManifestExists
+				script(type="module" src="/vite/@vite/client")
 
 		if Array.isArray(clientEntry.css)
 			each href in clientEntry.css
-				link(rel='stylesheet' href=`/assets/${href}`)
+				link(rel='stylesheet' href=`/vite/${href}`)
 
 		title
 			block title
diff --git a/packages/client/package.json b/packages/client/package.json
index 6ed9c5d1feff3bb971ed30851f7759bae309e7c5..87ef6e3638773d3ee548f2b8a3eea363fa6959c4 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -2,7 +2,7 @@
 	"name": "client",
 	"private": true,
 	"scripts": {
-		"watch": "vite build --watch --mode development",
+		"watch": "vite",
 		"build": "vite build",
 		"lint": "vue-tsc --noEmit && eslint --quiet \"src/**/*.{ts,vue}\""
 	},
diff --git a/packages/client/src/components/MkFileListForAdmin.vue b/packages/client/src/components/MkFileListForAdmin.vue
index b6429eaf8dd11e3df868ad7163fb53c8d7f0465e..4910506a9500baf21dd3eb3083172b2228616e66 100644
--- a/packages/client/src/components/MkFileListForAdmin.vue
+++ b/packages/client/src/components/MkFileListForAdmin.vue
@@ -34,7 +34,6 @@
 <script lang="ts" setup>
 import { computed } from 'vue';
 import * as Acct from 'misskey-js/built/acct';
-import MkSwitch from '@/components/ui/switch.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import bytes from '@/filters/bytes';
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index 07b808d55bdf6b3452f46b03b2ed611d38059c2a..6c1f54186cc8ef7c5817d44bb26bd3ef5f41355a 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -159,8 +159,6 @@ import {
 } from 'chart.js';
 import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
-import MagicGrid from 'magic-grid';
-import XMetrics from './metrics.vue';
 import XFederation from './overview.federation.vue';
 import XQueueChart from './overview.queue-chart.vue';
 import XUser from './overview.user.vue';
diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts
index f23c621131074ac651e0694766fd6e4c64eda47e..1acf5301b7a87f436db1e6dcc1dac109536723ec 100644
--- a/packages/client/vite.config.ts
+++ b/packages/client/vite.config.ts
@@ -1,4 +1,3 @@
-import * as fs from 'fs';
 import pluginVue from '@vitejs/plugin-vue';
 import { defineConfig } from 'vite';
 
@@ -9,11 +8,9 @@ import pluginJson5 from './vite.json5';
 const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
 
 export default defineConfig(({ command, mode }) => {
-	fs.mkdirSync(__dirname + '/../../built', { recursive: true });
-	fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8');
 
 	return {
-		base: '/assets/',
+		base: '/vite/',
 
 		plugins: [
 			pluginVue({
@@ -63,10 +60,10 @@ export default defineConfig(({ command, mode }) => {
 				},
 			},
 			cssCodeSplit: true,
-			outDir: __dirname + '/../../built/_client_dist_',
+			outDir: __dirname + '/../../built/_vite_',
 			assetsDir: '.',
 			emptyOutDir: false,
-			sourcemap: process.env.NODE_ENV !== 'production',
+			sourcemap: process.env.NODE_ENV === 'development',
 			reportCompressedSize: false,
 		},
 	};
diff --git a/scripts/build-pre.js b/scripts/build-pre.js
new file mode 100644
index 0000000000000000000000000000000000000000..e34a97738e0787e2af2633a5e7f8ef7a293ffff5
--- /dev/null
+++ b/scripts/build-pre.js
@@ -0,0 +1,5 @@
+const fs = require('fs');
+const meta = require('../package.json');
+
+fs.mkdirSync(__dirname + '/../built', { recursive: true });
+fs.writeFileSync(__dirname + '/../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8');
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index 456b88032b82c50109c5b4463ca16a491670d6bc..49b9957657d3ce206b16f840cde7d1afbac6ad58 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -12,4 +12,5 @@ const fs = require('fs');
 
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../.yarn/cache', { recursive: true, force: true });
 })();
diff --git a/scripts/dev.js b/scripts/dev.js
index b0fe12ee30c9a0423a80c3bf2fa8fcd6a39658cd..24e8914ee9a1794dfe5bd91326a53384f0e52b8c 100644
--- a/scripts/dev.js
+++ b/scripts/dev.js
@@ -1,4 +1,5 @@
 const execa = require('execa');
+const fs = require('fs');
 
 (async () => {
 	await execa('yarn', ['clean'], {
@@ -7,6 +8,12 @@ const execa = require('execa');
 		stderr: process.stderr,
 	});
 
+	await execa('yarn', ['build-pre'], {
+		cwd: __dirname + '/../',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
+
 	execa('yarn', ['dlx', 'gulp', 'watch'], {
 		cwd: __dirname + '/../',
 		stdout: process.stdout,
@@ -33,6 +40,9 @@ const execa = require('execa');
 
 	const start = async () => {
 		try {
+			const exist = fs.existsSync(__dirname + '/../packages/backend/built/boot/index.js')
+			if (!exist) throw new Error('not exist yet');
+
 			await execa('yarn', ['start'], {
 				cwd: __dirname + '/../',
 				stdout: process.stdout,
diff --git a/yarn.lock b/yarn.lock
index e2d2dea8f839d4d07613f4990c5a72ecc39128b9..eb021a707cde8702e2a055292027b6b52d1a408d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -917,6 +917,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fastify/http-proxy@npm:^8.4.0":
+  version: 8.4.0
+  resolution: "@fastify/http-proxy@npm:8.4.0"
+  dependencies:
+    "@fastify/reply-from": ^8.0.0
+    ws: ^8.4.2
+  checksum: 4bc4f0acac667c0c2f152e78342d8c7aeb4880d461227971ce31c85fe8d532fba616d2ef5224d6542f598a8a388d61954ed002003e2ce0695c15141e94a1a06b
+  languageName: node
+  linkType: hard
+
 "@fastify/multipart@npm:7.3.0":
   version: 7.3.0
   resolution: "@fastify/multipart@npm:7.3.0"
@@ -933,6 +943,21 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fastify/reply-from@npm:^8.0.0":
+  version: 8.3.1
+  resolution: "@fastify/reply-from@npm:8.3.1"
+  dependencies:
+    "@fastify/error": ^3.0.0
+    end-of-stream: ^1.4.4
+    fast-querystring: ^1.0.0
+    fastify-plugin: ^4.0.0
+    pump: ^3.0.0
+    tiny-lru: ^10.0.0
+    undici: ^5.5.1
+  checksum: debfc85b69946ecbad21dc2b01b2740b5a562258b5e3f00c452a88691525db650499cdf3bf09d85ae3f20455372925d6d7203265d0f00fa873f380be6e16e4d7
+  languageName: node
+  linkType: hard
+
 "@fastify/static@npm:6.6.0":
   version: 6.6.0
   resolution: "@fastify/static@npm:6.6.0"
@@ -4100,6 +4125,7 @@ __metadata:
     "@fastify/accepts": 4.1.0
     "@fastify/cookie": ^8.3.0
     "@fastify/cors": 8.2.0
+    "@fastify/http-proxy": ^8.4.0
     "@fastify/multipart": 7.3.0
     "@fastify/static": 6.6.0
     "@fastify/view": 7.3.0
@@ -16642,6 +16668,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"undici@npm:^5.5.1":
+  version: 5.14.0
+  resolution: "undici@npm:5.14.0"
+  dependencies:
+    busboy: ^1.6.0
+  checksum: 7a076e44d84b25844b4eb657034437b8b9bb91f17d347de474fdea1d4263ce7ae9406db79cd30de5642519277b4893f43073258bcc8fed420b295da3fdd11b26
+  languageName: node
+  linkType: hard
+
 "union-value@npm:^1.0.0":
   version: 1.0.1
   resolution: "union-value@npm:1.0.1"
@@ -17419,7 +17454,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ws@npm:8.11.0, ws@npm:^8.11.0":
+"ws@npm:8.11.0, ws@npm:^8.11.0, ws@npm:^8.4.2":
   version: 8.11.0
   resolution: "ws@npm:8.11.0"
   peerDependencies: