fix(frontend): restore per-route document titles in the SPA

When the multi-entry MPA collapsed into a single index.html, every
route inherited the static <title>3X-UI</title> from the shared shell,
so every panel page showed "hostname - 3X-UI" instead of the original
"hostname - Overview / Clients / Inbounds / ...".

usePageTitle reads the current pathname and rewrites document.title
on every navigation, matching the titles the deleted *.html files
used to carry. Mounted in PanelLayout so it covers all panel routes
without each page having to opt in.

The startup applyDocumentTitle() call in main.tsx is gone — the hook
sets the full "hostname - PageTitle" string itself.
This commit is contained in:
MHSanaei 2026-05-24 19:29:18 +02:00
parent 6a6f44c884
commit 77099f91e8
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
3 changed files with 24 additions and 2 deletions

View file

@ -0,0 +1,22 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const TITLES: Record<string, string> = {
'/': 'Overview',
'/inbounds': 'Inbounds',
'/clients': 'Clients',
'/nodes': 'Nodes',
'/settings': 'Settings',
'/xray': 'Xray Config',
'/api-docs': 'API Docs',
};
export function usePageTitle() {
const { pathname } = useLocation();
useEffect(() => {
const title = TITLES[pathname] || '3X-UI';
const host = window.location.hostname;
document.title = host ? `${host} - ${title}` : title;
}, [pathname]);
}

View file

@ -1,8 +1,10 @@
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { useWebSocketBridge } from '@/api/websocketBridge'; import { useWebSocketBridge } from '@/api/websocketBridge';
import { usePageTitle } from '@/hooks/usePageTitle';
export default function PanelLayout() { export default function PanelLayout() {
useWebSocketBridge(); useWebSocketBridge();
usePageTitle();
return <Outlet />; return <Outlet />;
} }

View file

@ -4,14 +4,12 @@ import { message } from 'antd';
import 'antd/dist/reset.css'; import 'antd/dist/reset.css';
import { setupAxios } from '@/api/axios-init.js'; import { setupAxios } from '@/api/axios-init.js';
import { applyDocumentTitle } from '@/utils';
import { readyI18n } from '@/i18n/react'; import { readyI18n } from '@/i18n/react';
import { ThemeProvider } from '@/hooks/useTheme'; import { ThemeProvider } from '@/hooks/useTheme';
import { QueryProvider } from '@/api/QueryProvider'; import { QueryProvider } from '@/api/QueryProvider';
import { router } from '@/routes'; import { router } from '@/routes';
setupAxios(); setupAxios();
applyDocumentTitle();
const messageContainer = document.getElementById('message'); const messageContainer = document.getElementById('message');
if (messageContainer) { if (messageContainer) {