3x-ui/frontend
MHSanaei c12d34a262
perf(frontend): lazy-load modals on inbounds / clients / index pages
Modals on the three list pages were imported statically, so the JS +
CSS for every form, info, qr, log, backup, metrics, system-history,
version, and config-text modal sat in the initial bundle even though
they're only needed after a click.

Converted those imports to React.lazy() and gated each modal with a
new LazyMount helper that mounts on first open and keeps the component
mounted thereafter so AntD close animations still play.

Build now emits a dedicated chunk per modal — InboundFormModal at
66 kB (13 kB gzipped) and InboundInfoModal at 23 kB (4 kB gzipped)
are the largest, totalling roughly 150 kB of code that no longer
parses on first paint. Profiler measured the inbounds-page React
render tree drop from ~444 ms to ~254 ms on a prod build.
2026-05-23 18:52:30 +02:00
..
src perf(frontend): lazy-load modals on inbounds / clients / index pages 2026-05-23 18:52:30 +02:00
.gitignore Vue3 migration (#4198) 2026-05-09 17:47:35 +02:00
api-docs.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
clients.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
eslint.config.js Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
inbounds.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
index.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
login.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
nodes.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
package-lock.json Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
package.json Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
README.md Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
settings.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
subpage.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
tsconfig.json Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
vite.config.js Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00
xray.html Frontend rewrite: React + TypeScript with AntD v6 (#4498) 2026-05-23 15:21:45 +02:00

3x-ui frontend

React 19 + Ant Design 6 + TypeScript + Vite 8. Multi-page app — one HTML entry per panel route — built into ../web/dist/ and embedded into the Go binary via embed.FS.

Dev

npm install
npm run dev

Vite serves on http://localhost:5173/. API calls and /panel/* routes proxy to the Go panel at http://localhost:2053/, so start the Go panel first (go run main.go) and then Vite.

The proxy auto-rewrites /panel, /panel/settings, /panel/inbounds, /panel/xray to the matching Vite-served HTML in dev mode (see MIGRATED_ROUTES in vite.config.js), so the sidebar's production-style links work without round-tripping through Go.

Production build

npm run build

Outputs to ../web/dist/ (HTML at the root, hashed JS/CSS under assets/). The Go binary embeds this directory at compile time and web/controller/dist.go serves the per-page HTML.

Type check and lint

npm run typecheck
npm run lint

tsc --noEmit against tsconfig.json (strict mode, jsx: "react-jsx", @/*src/* alias). ESLint 10 with eslint.config.js (flat config) — @eslint/js recommended plus typescript-eslint and eslint-plugin-react-hooks rules.

Layout

frontend/
├── *.html                 # Vite entry HTML, one per panel route
├── tsconfig.json
├── eslint.config.js
├── vite.config.js
└── src/
    ├── entries/           # Per-page bootstrap (createRoot + render)
    ├── pages/             # One folder per route, each with the page
    │   ├── index/         # component + helpers + sub-components
    │   ├── login/
    │   ├── inbounds/
    │   ├── clients/
    │   ├── xray/
    │   ├── nodes/
    │   ├── settings/
    │   ├── api-docs/
    │   └── sub/
    ├── components/        # Cross-page React components
    ├── hooks/             # Reusable hooks (useTheme, useWebSocket, …)
    ├── api/               # Axios setup, CSRF interceptor, WebSocket
    ├── i18n/              # react-i18next init (locales live in web/translation/)
    ├── models/            # Inbound, Outbound, Status, … domain classes
    ├── styles/            # Shared CSS modules (page-cards, …)
    └── utils/             # HttpUtil, ObjectUtil, LanguageManager, …

Adding a new page

  1. Add frontend/<page>.html referencing /src/entries/<page>.tsx.
  2. Add src/entries/<page>.tsx that imports the page component and mounts it with createRoot(...).render(...).
  3. Add the page component under src/pages/<page>/.
  4. Register the entry in rollupOptions.input in vite.config.js.
  5. If the page is reachable from the sidebar at /panel/<route>, add it to MIGRATED_ROUTES so the dev proxy serves the Vite HTML.
  6. Wire the Go controller to serveDistPage(c, "<page>.html").