diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json
index daffc597f7..503a212b7e 100644
--- a/packages/frontend/admin/package.json
+++ b/packages/frontend/admin/package.json
@@ -7,6 +7,7 @@
"@affine/core": "workspace:*",
"@affine/error": "workspace:*",
"@affine/graphql": "workspace:*",
+ "@affine/routes": "workspace:*",
"@blocksuite/icons": "^2.2.12",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.3",
@@ -50,7 +51,7 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.1",
"react-resizable-panels": "^2.1.7",
- "react-router-dom": "^6.28.0",
+ "react-router-dom": "^7.5.1",
"sonner": "^2.0.0",
"swr": "^2.2.5",
"vaul": "^1.1.1",
diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx
index f8ba35b90c..320f461516 100644
--- a/packages/frontend/admin/src/app.tsx
+++ b/packages/frontend/admin/src/app.tsx
@@ -1,11 +1,13 @@
import { Toaster } from '@affine/admin/components/ui/sonner';
-import { wrapCreateBrowserRouterV6 } from '@sentry/react';
+import { lazy, ROUTES } from '@affine/routes';
+import { withSentryReactRouterV7Routing } from '@sentry/react';
import { useEffect } from 'react';
import {
- createBrowserRouter as reactRouterCreateBrowserRouter,
+ BrowserRouter,
Navigate,
Outlet,
- RouterProvider,
+ Route,
+ Routes as ReactRouterRoutes,
useLocation,
} from 'react-router-dom';
import { toast } from 'sonner';
@@ -15,13 +17,28 @@ import { TooltipProvider } from './components/ui/tooltip';
import { isAdmin, useCurrentUser, useServerConfig } from './modules/common';
import { Layout } from './modules/layout';
-const createBrowserRouter = wrapCreateBrowserRouterV6(
- reactRouterCreateBrowserRouter
+export const Setup = lazy(
+ () => import(/* webpackChunkName: "setup" */ './modules/setup')
+);
+export const Accounts = lazy(
+ () => import(/* webpackChunkName: "accounts" */ './modules/accounts')
+);
+export const AI = lazy(
+ () => import(/* webpackChunkName: "ai" */ './modules/ai')
+);
+export const About = lazy(
+ () => import(/* webpackChunkName: "about" */ './modules/about')
+);
+export const Settings = lazy(
+ () => import(/* webpackChunkName: "settings" */ './modules/settings')
+);
+export const Auth = lazy(
+ () => import(/* webpackChunkName: "auth" */ './modules/auth')
);
-const _createBrowserRouter = window.SENTRY_RELEASE
- ? createBrowserRouter
- : reactRouterCreateBrowserRouter;
+const Routes = window.SENTRY_RELEASE
+ ? withSentryReactRouterV7Routing(ReactRouterRoutes)
+ : ReactRouterRoutes;
function AuthenticatedRoutes() {
const user = useCurrentUser();
@@ -58,57 +75,6 @@ function RootRoutes() {
return ;
}
-export const router = _createBrowserRouter(
- [
- {
- path: '/admin',
- element: ,
- children: [
- {
- path: '/admin/auth',
- lazy: () => import('./modules/auth'),
- },
- {
- path: '/admin/setup',
- lazy: () => import('./modules/setup'),
- },
- {
- path: '/admin/*',
- element: ,
- children: [
- {
- path: 'accounts',
- lazy: () => import('./modules/accounts'),
- },
- {
- path: 'ai',
- lazy: () => import('./modules/ai'),
- },
- {
- path: 'about',
- lazy: () => import('./modules/about'),
- },
- {
- path: 'settings',
- children: [
- {
- path: '*',
- lazy: () => import('./modules/settings'),
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- {
- future: {
- v7_normalizeFormMethod: true,
- },
- }
-);
-
export const App = () => {
return (
@@ -118,7 +84,28 @@ export const App = () => {
revalidateOnMount: false,
}}
>
-
+
+
+ }>
+ } />
+ } />
+ }>
+ } />
+ } />
+ } />
+ }
+ >
+ }
+ />
+
+
+
+
+
diff --git a/packages/frontend/admin/src/modules/settings/index.tsx b/packages/frontend/admin/src/modules/settings/index.tsx
index 929942e64a..4e523f2797 100644
--- a/packages/frontend/admin/src/modules/settings/index.tsx
+++ b/packages/frontend/admin/src/modules/settings/index.tsx
@@ -62,7 +62,7 @@ export function SettingsPage() {
);
}
-export const AdminPanel = ({
+const AdminPanel = ({
appConfig,
patchedAppConfig,
onUpdate,
diff --git a/packages/frontend/admin/src/modules/setup/form.tsx b/packages/frontend/admin/src/modules/setup/form.tsx
index 57e6bc6e62..4a398de291 100644
--- a/packages/frontend/admin/src/modules/setup/form.tsx
+++ b/packages/frontend/admin/src/modules/setup/form.tsx
@@ -165,7 +165,7 @@ export const Form = () => {
passwordValue,
]);
- const onPrevious = useCallback(() => {
+ const onPrevious = useAsyncCallback(async () => {
if (current === count) {
if (serverConfig.initialized === true) {
return navigate('/admin', { replace: true });
diff --git a/packages/frontend/admin/tsconfig.json b/packages/frontend/admin/tsconfig.json
index 85cab81c21..10c9b44c16 100644
--- a/packages/frontend/admin/tsconfig.json
+++ b/packages/frontend/admin/tsconfig.json
@@ -12,6 +12,7 @@
{ "path": "../core" },
{ "path": "../../common/error" },
{ "path": "../../common/graphql" },
+ { "path": "../routes" },
{ "path": "../../common/infra" }
]
}
diff --git a/packages/frontend/routes/package.json b/packages/frontend/routes/package.json
index e70b33168c..1bf35682ea 100644
--- a/packages/frontend/routes/package.json
+++ b/packages/frontend/routes/package.json
@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"exports": {
- ".": "./src/routes.ts"
+ ".": "./src/index.ts"
},
"scripts": {
"build": "r build.ts"
@@ -16,7 +16,8 @@
"query-string": "^9.1.1",
"vitest": "^3.0.6"
},
- "optionalDependencies": {
- "react-router-dom": "^6.28.0"
+ "peerDependencies": {
+ "react": "^19.1.0",
+ "react-router-dom": "^7.5.1"
}
}
diff --git a/packages/frontend/routes/routes.json b/packages/frontend/routes/routes.json
index 14a8e00001..8c58ee25b8 100644
--- a/packages/frontend/routes/routes.json
+++ b/packages/frontend/routes/routes.json
@@ -5,6 +5,8 @@
"admin": {
"route": "admin",
"children": {
+ "auth": "auth",
+ "setup": "setup",
"accounts": "accounts",
"ai": "ai",
"settings": {
diff --git a/packages/frontend/routes/src/index.ts b/packages/frontend/routes/src/index.ts
new file mode 100644
index 0000000000..c6cbefb6cf
--- /dev/null
+++ b/packages/frontend/routes/src/index.ts
@@ -0,0 +1,2 @@
+export * from './lazy';
+export * from './routes';
diff --git a/packages/frontend/routes/src/lazy.ts b/packages/frontend/routes/src/lazy.ts
new file mode 100644
index 0000000000..01821bfe83
--- /dev/null
+++ b/packages/frontend/routes/src/lazy.ts
@@ -0,0 +1,37 @@
+import React, {
+ type ComponentProps,
+ type ComponentType,
+ lazy as reactLazy,
+ Suspense,
+} from 'react';
+
+export function lazy>(
+ factory: () => Promise>,
+ fallback?: React.ReactNode
+) {
+ const LazyComponent = reactLazy(() =>
+ factory().then(mod => {
+ if ('default' in mod) {
+ return { default: mod.default };
+ } else {
+ const components = Object.values(mod);
+ if (components.length > 1) {
+ console.warn('Lazy loaded module has more then one exports');
+ }
+ return {
+ default: components[0],
+ };
+ }
+ })
+ );
+
+ return function LazyRoute(props: ComponentProps) {
+ return React.createElement(
+ Suspense,
+ {
+ fallback,
+ },
+ React.createElement(LazyComponent, props)
+ );
+ };
+}
diff --git a/packages/frontend/routes/src/routes.ts b/packages/frontend/routes/src/routes.ts
index a55b862eb5..8a0e586e99 100644
--- a/packages/frontend/routes/src/routes.ts
+++ b/packages/frontend/routes/src/routes.ts
@@ -9,10 +9,13 @@ export const ROUTES = {
index: '/',
admin: {
index: '/admin',
+ auth: '/admin/auth',
+ setup: '/admin/setup',
accounts: '/admin/accounts',
ai: '/admin/ai',
settings: { index: '/admin/settings', module: '/admin/settings/:module' },
about: '/admin/about',
+ notFound: '/admin/404',
},
};
// #endregion
@@ -22,10 +25,13 @@ export const RELATIVE_ROUTES = {
index: '/',
admin: {
index: 'admin',
+ auth: 'auth',
+ setup: 'setup',
accounts: 'accounts',
ai: 'ai',
settings: { index: 'settings', module: ':module' },
about: 'about',
+ notFound: '404',
},
};
// #endregion
@@ -33,6 +39,8 @@ export const RELATIVE_ROUTES = {
// #region Path Factories
const home = () => '/';
const admin = () => '/admin';
+admin.auth = () => '/admin/auth';
+admin.setup = () => '/admin/setup';
admin.accounts = () => '/admin/accounts';
admin.ai = () => '/admin/ai';
const admin_settings = () => '/admin/settings';
@@ -40,5 +48,6 @@ admin_settings.module = (params: { module: string }) =>
`/admin/settings/${params.module}`;
admin.settings = admin_settings;
admin.about = () => '/admin/about';
+admin.notFound = () => '/admin/404';
export const FACTORIES = { admin, home };
// #endregion
diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts
index d4f9d8c58c..b42fb972aa 100644
--- a/tools/utils/src/workspace.gen.ts
+++ b/tools/utils/src/workspace.gen.ts
@@ -1035,6 +1035,7 @@ export const PackageList = [
'packages/frontend/core',
'packages/common/error',
'packages/common/graphql',
+ 'packages/frontend/routes',
'packages/common/infra',
],
},
diff --git a/yarn.lock b/yarn.lock
index da39da46c5..8e8e015327 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -180,6 +180,7 @@ __metadata:
"@affine/core": "workspace:*"
"@affine/error": "workspace:*"
"@affine/graphql": "workspace:*"
+ "@affine/routes": "workspace:*"
"@blocksuite/icons": "npm:^2.2.12"
"@radix-ui/react-accordion": "npm:^1.2.2"
"@radix-ui/react-alert-dialog": "npm:^1.1.3"
@@ -227,7 +228,7 @@ __metadata:
react-dom: "npm:^19.0.0"
react-hook-form: "npm:^7.54.1"
react-resizable-panels: "npm:^2.1.7"
- react-router-dom: "npm:^6.28.0"
+ react-router-dom: "npm:^7.5.1"
shadcn-ui: "npm:^0.9.4"
sonner: "npm:^2.0.0"
swr: "npm:^2.2.5"
@@ -852,7 +853,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@affine/routes@workspace:packages/frontend/routes":
+"@affine/routes@workspace:*, @affine/routes@workspace:packages/frontend/routes":
version: 0.0.0-use.local
resolution: "@affine/routes@workspace:packages/frontend/routes"
dependencies:
@@ -860,11 +861,10 @@ __metadata:
"@affine-tools/utils": "workspace:*"
path-to-regexp: "npm:^8.2.0"
query-string: "npm:^9.1.1"
- react-router-dom: "npm:^6.28.0"
vitest: "npm:^3.0.6"
- dependenciesMeta:
- react-router-dom:
- optional: true
+ peerDependencies:
+ react: ^19.1.0
+ react-router-dom: ^7.5.1
languageName: unknown
linkType: soft
@@ -19052,6 +19052,13 @@ __metadata:
languageName: node
linkType: hard
+"cookie@npm:^1.0.1":
+ version: 1.0.2
+ resolution: "cookie@npm:1.0.2"
+ checksum: 10/f5817cdc84d8977761b12549eba29435e675e65c7fef172bc31737788cd8adc83796bf8abe6d950554e7987325ad2d9ac2971c5bd8ff0c4f81c145f82e4ab1be
+ languageName: node
+ linkType: hard
+
"cookiejar@npm:^2.1.4":
version: 2.1.4
resolution: "cookiejar@npm:2.1.4"
@@ -29835,6 +29842,18 @@ __metadata:
languageName: node
linkType: hard
+"react-router-dom@npm:^7.5.1":
+ version: 7.5.1
+ resolution: "react-router-dom@npm:7.5.1"
+ dependencies:
+ react-router: "npm:7.5.1"
+ peerDependencies:
+ react: ">=18"
+ react-dom: ">=18"
+ checksum: 10/a564fd2b3cfa9a4b79dbdbae53b7504fcd678b2aba69a696ae494c228927999b3be9bc4e304a63a0b2247a2943c241bf3bb4fe25d62092de64e16309654f0f2d
+ languageName: node
+ linkType: hard
+
"react-router@npm:6.30.0":
version: 6.30.0
resolution: "react-router@npm:6.30.0"
@@ -29846,6 +29865,23 @@ __metadata:
languageName: node
linkType: hard
+"react-router@npm:7.5.1":
+ version: 7.5.1
+ resolution: "react-router@npm:7.5.1"
+ dependencies:
+ cookie: "npm:^1.0.1"
+ set-cookie-parser: "npm:^2.6.0"
+ turbo-stream: "npm:2.4.0"
+ peerDependencies:
+ react: ">=18"
+ react-dom: ">=18"
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ checksum: 10/c5c3bacbb93058f4cbf93557ea4d3376393e071aa59e2f7044060cc4073b2a253fcd29c7c0b89d6c373d56dc7e2494729871e5a72be66988b1c0bd726f7cfc0e
+ languageName: node
+ linkType: hard
+
"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3":
version: 2.2.3
resolution: "react-style-singleton@npm:2.2.3"
@@ -31033,7 +31069,7 @@ __metadata:
languageName: node
linkType: hard
-"set-cookie-parser@npm:^2.7.1":
+"set-cookie-parser@npm:^2.6.0, set-cookie-parser@npm:^2.7.1":
version: 2.7.1
resolution: "set-cookie-parser@npm:2.7.1"
checksum: 10/c92b1130032693342bca13ea1b1bc93967ab37deec4387fcd8c2a843c0ef2fd9a9f3df25aea5bb3976cd05a91c2cf4632dd6164d6e1814208fb7d7e14edd42b4
@@ -32941,6 +32977,13 @@ __metadata:
languageName: node
linkType: hard
+"turbo-stream@npm:2.4.0":
+ version: 2.4.0
+ resolution: "turbo-stream@npm:2.4.0"
+ checksum: 10/7079bbc82b58340f783144cd669cc7e598288523103a8d68bb8a4c6bb28c64eccb71d389b33aab07788d3a9030638b795709e15cb8486f722b1cdac59cb58afc
+ languageName: node
+ linkType: hard
+
"tweakpane@npm:^4.0.4":
version: 4.0.5
resolution: "tweakpane@npm:4.0.5"