aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2024-10-16 11:52:23 +0200
committersonartech <sonartech@sonarsource.com>2024-10-22 20:03:09 +0000
commit54166222f6a6f6e6feaf482b81f89bf732045ff3 (patch)
tree275e0fc6c0cdbbc66fdf7b4fb64435d74e5705a6 /server/sonar-web/src/main/js
parentfb204bcee8178d160c88323f6ba8e471935bd982 (diff)
downloadsonarqube-54166222f6a6f6e6feaf482b81f89bf732045ff3.tar.gz
sonarqube-54166222f6a6f6e6feaf482b81f89bf732045ff3.zip
SONAR-23205 Add lazy loading on most routes to improve build and dev server perfs
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/code/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/dependencies/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/groups/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/routes.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/routes.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projectDump/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectKey/routes.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/routes.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/routes.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/settings/routes.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/system/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/users/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/web-api/routes.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/routes.tsx6
-rw-r--r--server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx71
36 files changed, 313 insertions, 52 deletions
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index 605c7910ce1..d2031afbc0b 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -33,10 +33,10 @@ import {
createBrowserRouter,
createRoutesFromElements,
} from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
import accountRoutes from '../../apps/account/routes';
import auditLogsRoutes from '../../apps/audit-logs/routes';
import backgroundTasksRoutes from '../../apps/background-tasks/routes';
-import ChangeAdminPasswordApp from '../../apps/change-admin-password/ChangeAdminPasswordApp';
import codeRoutes from '../../apps/code/routes';
import codingRulesRoutes from '../../apps/coding-rules/routes';
import componentMeasuresRoutes from '../../apps/component-measures/routes';
@@ -50,11 +50,11 @@ import permissionTemplatesRoutes from '../../apps/permission-templates/routes';
import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes';
import projectActivityRoutes from '../../apps/projectActivity/routes';
import projectBranchesRoutes from '../../apps/projectBranches/routes';
-import ProjectDeletionApp from '../../apps/projectDeletion/App';
+import projectDeletionRoutes from '../../apps/projectDeletion/routes';
import projectDumpRoutes from '../../apps/projectDump/routes';
import projectInfoRoutes from '../../apps/projectInformation/routes';
-import ProjectKeyApp from '../../apps/projectKey/ProjectKeyApp';
-import ProjectLinksApp from '../../apps/projectLinks/ProjectLinksApp';
+import projectKeyRoutes from '../../apps/projectKey/routes';
+import projectLinksRoutes from '../../apps/projectLinks/routes';
import projectNewCodeDefinitionRoutes from '../../apps/projectNewCode/routes';
import projectQualityGateRoutes from '../../apps/projectQualityGate/routes';
import projectQualityProfilesRoutes from '../../apps/projectQualityProfiles/routes';
@@ -62,7 +62,7 @@ import projectsRoutes from '../../apps/projects/routes';
import projectsManagementRoutes from '../../apps/projectsManagement/routes';
import qualityGatesRoutes from '../../apps/quality-gates/routes';
import qualityProfilesRoutes from '../../apps/quality-profiles/routes';
-import SecurityHotspotsApp from '../../apps/security-hotspots/SecurityHotspotsApp';
+import securityHotspotsRoutes from '../../apps/security-hotspots/routes';
import sessionsRoutes from '../../apps/sessions/routes';
import settingsRoutes from '../../apps/settings/routes';
import systemRoutes from '../../apps/system/routes';
@@ -81,17 +81,13 @@ import AdminContainer from '../components/AdminContainer';
import App from '../components/App';
import ComponentContainer from '../components/ComponentContainer';
import DocumentationRedirect from '../components/DocumentationRedirect';
-import FormattingHelp from '../components/FormattingHelp';
import GlobalContainer from '../components/GlobalContainer';
import Landing from '../components/Landing';
import MigrationContainer from '../components/MigrationContainer';
import NonAdminPagesContainer from '../components/NonAdminPagesContainer';
import NotFound from '../components/NotFound';
-import PluginRiskConsent from '../components/PluginRiskConsent';
import ProjectAdminContainer from '../components/ProjectAdminContainer';
-import ResetPassword from '../components/ResetPassword';
import SimpleContainer from '../components/SimpleContainer';
-import SonarLintConnection from '../components/SonarLintConnection';
import { DEFAULT_APP_STATE } from '../components/app-state/AppStateContext';
import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
import {
@@ -123,8 +119,12 @@ function renderComponentRoutes() {
element={<ProjectPageExtension />}
/>
{projectIssuesRoutes()}
+<<<<<<< HEAD
{dependenciesRoutes()}
<Route path="security_hotspots" element={<SecurityHotspotsApp />} />
+=======
+ {securityHotspotsRoutes()}
+>>>>>>> 6803c465323 (SONAR-23205 Add lazy loading on most routes to improve build and dev server perfs)
{projectQualityGateRoutes()}
{projectQualityProfilesRoutes()}
{projectInfoRoutes()}
@@ -144,9 +144,9 @@ function renderComponentRoutes() {
{settingsRoutes()}
{webhooksRoutes()}
- <Route path="deletion" element={<ProjectDeletionApp />} />
- <Route path="links" element={<ProjectLinksApp />} />
- <Route path="key" element={<ProjectKeyApp />} />
+ {projectDeletionRoutes()}
+ {projectLinksRoutes()}
+ {projectKeyRoutes()}
</Route>
{projectPermissionsRoutes()}
</Route>
@@ -185,6 +185,14 @@ function renderRedirects() {
);
}
+const FormattingHelp = lazyLoadComponent(() => import('../components/FormattingHelp'));
+const SonarLintConnection = lazyLoadComponent(() => import('../components/SonarLintConnection'));
+const ResetPassword = lazyLoadComponent(() => import('../components/ResetPassword'));
+const ChangeAdminPasswordApp = lazyLoadComponent(
+ () => import('../../apps/change-admin-password/ChangeAdminPasswordApp'),
+);
+const PluginRiskConsent = lazyLoadComponent(() => import('../components/PluginRiskConsent'));
+
const router = createBrowserRouter(
createRoutesFromElements(
<>
diff --git a/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx
index 0eb7f96e57d..7178d9ff58d 100644
--- a/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/audit-logs/routes.tsx
@@ -19,8 +19,10 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import AuditApp from './components/AuditApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
const routes = () => <Route path="audit" element={<AuditApp />} />;
+const AuditApp = lazyLoadComponent(() => import('./components/AuditApp'));
+
export default routes;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx
index d31a23bf1fc..f50eb2fb0f9 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import BackgroundTasksApp from './components/BackgroundTasksApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const BackgroundTasksApp = lazyLoadComponent(() => import('./components/BackgroundTasksApp'));
const routes = () => <Route path="background_tasks" element={<BackgroundTasksApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/code/routes.tsx b/server/sonar-web/src/main/js/apps/code/routes.tsx
index 7710f63cdd3..11c029616c3 100644
--- a/server/sonar-web/src/main/js/apps/code/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/code/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import CodeApp from './components/CodeApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const CodeApp = lazyLoadComponent(() => import('./components/CodeApp'));
const routes = () => <Route path="code" element={<CodeApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
index 8ac74338daa..67a18a55a23 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
@@ -19,10 +19,12 @@
*/
import React, { useEffect } from 'react';
import { Route, useLocation, useNavigate } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
import { RawQuery } from '~sonar-aligned/types/router';
-import CodingRulesApp from './components/CodingRulesApp';
import { parseQuery, serializeQuery } from './query';
+const CodingRulesApp = lazyLoadComponent(() => import('./components/CodingRulesApp'));
+
const EXPECTED_SPLIT_PARTS = 2;
function parseHash(hash: string): RawQuery {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.tsx b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx
index a547a34802e..debc43d2744 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/routes.tsx
@@ -19,10 +19,12 @@
*/
import React from 'react';
import { Navigate, Route, useParams, useSearchParams } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
import { searchParamsToQuery } from '~sonar-aligned/helpers/router';
import NavigateWithParams from '../../app/utils/NavigateWithParams';
import { omitNil } from '../../helpers/request';
-import ComponentMeasuresApp from './components/ComponentMeasuresApp';
+
+const ComponentMeasuresApp = lazyLoadComponent(() => import('./components/ComponentMeasuresApp'));
const routes = () => (
<Route path="component_measures">
diff --git a/server/sonar-web/src/main/js/apps/dependencies/routes.tsx b/server/sonar-web/src/main/js/apps/dependencies/routes.tsx
index 27525c61107..0c9ab45fb5c 100644
--- a/server/sonar-web/src/main/js/apps/dependencies/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/dependencies/routes.tsx
@@ -20,7 +20,9 @@
import React from 'react';
import { Route } from 'react-router-dom';
-import DependenciesApp from './DependenciesApp';
+import { lazyLoadComponent } from '../../sonar-aligned/helpers/lazyLoadComponent';
+
+const DependenciesApp = lazyLoadComponent(() => import('./DependenciesApp'));
export const dependenciesRoutes = () => <Route path="dependencies" element={<DependenciesApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/groups/routes.tsx b/server/sonar-web/src/main/js/apps/groups/routes.tsx
index db9738ea9d3..7f46c245ae3 100644
--- a/server/sonar-web/src/main/js/apps/groups/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import GroupsApp from './GroupsApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const GroupsApp = lazyLoadComponent(() => import('./GroupsApp'));
const routes = () => <Route path="groups" element={<GroupsApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/issues/routes.tsx b/server/sonar-web/src/main/js/apps/issues/routes.tsx
index 7f30e41a0db..48cbe65591f 100644
--- a/server/sonar-web/src/main/js/apps/issues/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/routes.tsx
@@ -19,9 +19,11 @@
*/
import React, { useEffect } from 'react';
import { Route, useNavigate, useSearchParams } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
import { omitNil } from '../../helpers/request';
import { IssueType } from '../../types/issues';
-import IssuesApp from './components/IssuesApp';
+
+const IssuesApp = lazyLoadComponent(() => import('./components/IssuesApp'));
export const globalIssuesRoutes = () => <Route path="issues" element={<IssuesApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/routes.tsx b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx
index 39fa3ef5239..8dedc452273 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import MarketplaceAppContainer from './MarketplaceAppContainer';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const MarketplaceAppContainer = lazyLoadComponent(() => import('./MarketplaceAppContainer'));
export const routes = () => <Route path="marketplace" element={<MarketplaceAppContainer />} />;
diff --git a/server/sonar-web/src/main/js/apps/overview/routes.tsx b/server/sonar-web/src/main/js/apps/overview/routes.tsx
index a0434016ce6..1a2249ef833 100644
--- a/server/sonar-web/src/main/js/apps/overview/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import App from './components/App';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const App = lazyLoadComponent(() => import('./components/App'));
const routes = () => <Route path="dashboard" element={<App />} />;
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx
index 7bd12ec84eb..9dbdef25593 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/permission-templates/routes.tsx
@@ -19,7 +19,11 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import PermissionTemplatesApp from './components/PermissionTemplatesApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const PermissionTemplatesApp = lazyLoadComponent(
+ () => import('./components/PermissionTemplatesApp'),
+);
const routes = () => <Route path="permission_templates" element={<PermissionTemplatesApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.tsx b/server/sonar-web/src/main/js/apps/permissions/routes.tsx
index 0a46773795e..c2fa8129d22 100644
--- a/server/sonar-web/src/main/js/apps/permissions/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/routes.tsx
@@ -19,8 +19,14 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import GlobalPermissionsApp from './global/components/PermissionsGlobalApp';
-import PermissionsProjectApp from './project/components/PermissionsProjectApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const GlobalPermissionsApp = lazyLoadComponent(
+ () => import('./global/components/PermissionsGlobalApp'),
+);
+const PermissionsProjectApp = lazyLoadComponent(
+ () => import('./project/components/PermissionsProjectApp'),
+);
export const globalPermissionsRoutes = () => (
<Route path="permissions" element={<GlobalPermissionsApp />} />
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx
index 3bd17ae6488..31cbc16ae8c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectActivityApp from './components/ProjectActivityApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectActivityApp = lazyLoadComponent(() => import('./components/ProjectActivityApp'));
const routes = () => <Route path="project/activity" element={<ProjectActivityApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx
index 0e278472115..5efbbd4a633 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectBranchesApp from './ProjectBranchesApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectBranchesApp = lazyLoadComponent(() => import('./ProjectBranchesApp'));
const routes = () => <Route path="branches" element={<ProjectBranchesApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx
new file mode 100644
index 00000000000..d6974af0f41
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/routes.tsx
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const App = lazyLoadComponent(() => import('./App'));
+
+const routes = () => <Route path="deletion" element={<App />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectDump/routes.tsx b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx
index 1cb748338c7..b757c6c7f93 100644
--- a/server/sonar-web/src/main/js/apps/projectDump/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDump/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectDumpApp from './ProjectDumpApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectDumpApp = lazyLoadComponent(() => import('./ProjectDumpApp'));
const routes = () => <Route path="import_export" element={<ProjectDumpApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx b/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx
index 903beaf840e..61f6295b5b1 100644
--- a/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectInformation/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectInformationApp from './ProjectInformationApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectInformationApp = lazyLoadComponent(() => import('./ProjectInformationApp'));
const routes = () => <Route path="project/information" element={<ProjectInformationApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectKey/routes.tsx b/server/sonar-web/src/main/js/apps/projectKey/routes.tsx
new file mode 100644
index 00000000000..592c0751106
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectKey/routes.tsx
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectKeyApp = lazyLoadComponent(() => import('./ProjectKeyApp'));
+
+const routes = () => <Route path="key" element={<ProjectKeyApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx b/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx
new file mode 100644
index 00000000000..d7718698ade
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectLinks/routes.tsx
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectLinksApp = lazyLoadComponent(() => import('./ProjectLinksApp'));
+
+const routes = () => <Route path="links" element={<ProjectLinksApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx
index 9ab18067b63..75666bd89fc 100644
--- a/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectNewCode/routes.tsx
@@ -19,7 +19,11 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectNewCodeDefinitionApp from './components/ProjectNewCodeDefinitionApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectNewCodeDefinitionApp = lazyLoadComponent(
+ () => import('./components/ProjectNewCodeDefinitionApp'),
+);
const routes = () => <Route path="baseline" element={<ProjectNewCodeDefinitionApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx
index 67db45e1f78..dbe8257b94f 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectQualityGateApp from './ProjectQualityGateApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectQualityGateApp = lazyLoadComponent(() => import('./ProjectQualityGateApp'));
const routes = () => <Route path="project/quality_gate" element={<ProjectQualityGateApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx
index 11b8f414d2c..8acac8ae60b 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectQualityProfilesApp from './ProjectQualityProfilesApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectQualityProfilesApp = lazyLoadComponent(() => import('./ProjectQualityProfilesApp'));
const routes = () => (
<Route path="project/quality_profiles" element={<ProjectQualityProfilesApp />} />
diff --git a/server/sonar-web/src/main/js/apps/projects/routes.tsx b/server/sonar-web/src/main/js/apps/projects/routes.tsx
index 62a2cb6ceba..cf062a6bc9e 100644
--- a/server/sonar-web/src/main/js/apps/projects/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/routes.tsx
@@ -19,12 +19,16 @@
*/
import React from 'react';
import { Navigate, Route } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
import { save } from '../../helpers/storage';
-import CreateProjectPage from '../create/project/CreateProjectPage';
-import DefaultPageSelector from './components/DefaultPageSelector';
-import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER } from './utils';
+const DefaultPageSelector = lazyLoadComponent(() => import('./components/DefaultPageSelector'));
+const FavoriteProjectsContainer = lazyLoadComponent(
+ () => import('./components/FavoriteProjectsContainer'),
+);
+const CreateProjectPage = lazyLoadComponent(() => import('../create/project/CreateProjectPage'));
+
function PersistNavigate() {
save(PROJECTS_DEFAULT_FILTER, PROJECTS_ALL);
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
index 5e766881a53..0a3c7a85cea 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ProjectManagementApp from './ProjectManagementApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const ProjectManagementApp = lazyLoadComponent(() => import('./ProjectManagementApp'));
const routes = () => <Route path="projects_management" element={<ProjectManagementApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx b/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx
index c3cf1a8f8fd..4765280f5fb 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import App from './components/App';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const App = lazyLoadComponent(() => import('./components/App'));
const routes = () => (
<Route path="quality_gates">
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx
index a6182ec8110..7909d2d3b96 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/routes.tsx
@@ -19,12 +19,14 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import ChangelogContainer from './changelog/ChangelogContainer';
-import ComparisonContainer from './compare/ComparisonContainer';
-import ProfileContainer from './components/ProfileContainer';
-import QualityProfilesApp from './components/QualityProfilesApp';
-import ProfileDetails from './details/ProfileDetails';
-import HomeContainer from './home/HomeContainer';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const QualityProfilesApp = lazyLoadComponent(() => import('./components/QualityProfilesApp'));
+const HomeContainer = lazyLoadComponent(() => import('./home/HomeContainer'));
+const ProfileContainer = lazyLoadComponent(() => import('./components/ProfileContainer'));
+const ProfileDetails = lazyLoadComponent(() => import('./details/ProfileDetails'));
+const ChangelogContainer = lazyLoadComponent(() => import('./changelog/ChangelogContainer'));
+const ComparisonContainer = lazyLoadComponent(() => import('./compare/ComparisonContainer'));
export enum QualityProfilePath {
SHOW = 'show',
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx
new file mode 100644
index 00000000000..7d75b302b05
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/routes.tsx
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const SecurityHotspotsApp = lazyLoadComponent(() => import('./SecurityHotspotsApp'));
+
+const routes = () => <Route path="security_hotspots" element={<SecurityHotspotsApp />} />;
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.tsx b/server/sonar-web/src/main/js/apps/settings/routes.tsx
index 6e01fcb5b8d..a2ce4ea9874 100644
--- a/server/sonar-web/src/main/js/apps/settings/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/routes.tsx
@@ -19,8 +19,10 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import SettingsApp from './components/SettingsApp';
-import EncryptionApp from './encryption/EncryptionApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const SettingsApp = lazyLoadComponent(() => import('./components/SettingsApp'));
+const EncryptionApp = lazyLoadComponent(() => import('./encryption/EncryptionApp'));
const routes = () => (
<Route path="settings">
diff --git a/server/sonar-web/src/main/js/apps/system/routes.tsx b/server/sonar-web/src/main/js/apps/system/routes.tsx
index 55351850983..cf8319590a6 100644
--- a/server/sonar-web/src/main/js/apps/system/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/system/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import SystemApp from './components/SystemApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const SystemApp = lazyLoadComponent(() => import('./components/SystemApp'));
export const routes = () => <Route path="system" element={<SystemApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.tsx b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx
index 3556deb36d8..d08cadabfc4 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import TutorialsApp from './components/TutorialsApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const TutorialsApp = lazyLoadComponent(() => import('./components/TutorialsApp'));
const routes = () => <Route path="tutorials" element={<TutorialsApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/users/routes.tsx b/server/sonar-web/src/main/js/apps/users/routes.tsx
index 337bec0bf33..3920d1ca079 100644
--- a/server/sonar-web/src/main/js/apps/users/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/users/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import UsersApp from './UsersApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const UsersApp = lazyLoadComponent(() => import('./UsersApp'));
export const routes = () => <Route path="users" element={<UsersApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx b/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx
index 36df5337c4c..e85d40f8c42 100644
--- a/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api-v2/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import WebApiApp from './WebApiApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const WebApiApp = lazyLoadComponent(() => import('./WebApiApp'));
const routes = () => <Route path="web_api_v2" element={<WebApiApp />} />;
diff --git a/server/sonar-web/src/main/js/apps/web-api/routes.tsx b/server/sonar-web/src/main/js/apps/web-api/routes.tsx
index 3468f7ce9d9..6f2537898c0 100644
--- a/server/sonar-web/src/main/js/apps/web-api/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/web-api/routes.tsx
@@ -19,7 +19,9 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import WebApiApp from './components/WebApiApp';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
+
+const WebApiApp = lazyLoadComponent(() => import('./components/WebApiApp'));
const routes = () => (
<Route path="web_api">
diff --git a/server/sonar-web/src/main/js/apps/webhooks/routes.tsx b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx
index 1aaab6eb14a..ce4b06474ad 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/routes.tsx
@@ -19,8 +19,10 @@
*/
import React from 'react';
import { Route } from 'react-router-dom';
-import App from './components/App';
+import { lazyLoadComponent } from '~sonar-aligned/helpers/lazyLoadComponent';
-export const routes = () => <Route path="webhooks" element={<App />} />;
+const WebhooksApp = lazyLoadComponent(() => import('./components/App'));
+
+export const routes = () => <Route path="webhooks" element={<WebhooksApp />} />;
export default routes;
diff --git a/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx b/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx
new file mode 100644
index 00000000000..0d47d9a8b89
--- /dev/null
+++ b/server/sonar-web/src/main/js/sonar-aligned/helpers/lazyLoadComponent.tsx
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { FlagMessage } from 'design-system';
+import React, { Component, lazy, Suspense } from 'react';
+import { translate } from '../../helpers/l10n';
+import { requestTryAndRepeatUntil } from '../../helpers/request';
+
+export function lazyLoadComponent<T extends React.ComponentType<any>>(
+ factory: () => Promise<{ default: T }>,
+ displayName?: string,
+) {
+ const LazyComponent = lazy(() =>
+ requestTryAndRepeatUntil(factory, { max: 2, slowThreshold: 2 }, () => true),
+ );
+
+ function LazyComponentWrapper(props: React.ComponentProps<T>) {
+ return (
+ <LazyErrorBoundary>
+ <Suspense fallback={null}>
+ <LazyComponent {...props} />
+ </Suspense>
+ </LazyErrorBoundary>
+ );
+ }
+
+ LazyComponentWrapper.displayName = displayName;
+ return LazyComponentWrapper;
+}
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+}
+
+export class LazyErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
+ state: ErrorBoundaryState = { hasError: false };
+
+ static getDerivedStateFromError() {
+ // Update state so the next render will show the fallback UI.
+ return { hasError: true };
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+ <FlagMessage variant="error">{translate('default_component_error_message')}</FlagMessage>
+ );
+ }
+ return this.props.children;
+ }
+}