From 1961e30ac1221aab6465994c16954fd1277593ee Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 18 May 2026 10:17:13 +0530 Subject: [PATCH 1/6] perf: lazy load page components and playground dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React.lazy for ApiLayout, ApiPage, DocsLayout, DocsPage, LandingPage in App.tsx. Lazy load PlaygroundDialog in Layout.tsx — CodeMirror only loads when user opens the dialog. entry-client chunk: 1MB+ → 325KB (101KB gzip). Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/App.tsx | 32 ++++++++++-------- .../chronicle/src/themes/default/Layout.tsx | 33 +++++++++++-------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/chronicle/src/server/App.tsx b/packages/chronicle/src/server/App.tsx index 16db22ef..14e1953c 100644 --- a/packages/chronicle/src/server/App.tsx +++ b/packages/chronicle/src/server/App.tsx @@ -1,18 +1,20 @@ import '@raystack/apsara/normalize.css'; import '@raystack/apsara/style.css'; import { ThemeProvider } from '@raystack/apsara'; +import { lazy, Suspense } from 'react'; import { Navigate, useLocation } from 'react-router'; import { Head } from '@/lib/head'; import { usePageContext } from '@/lib/page-context'; import { resolveRoute, RouteType } from '@/lib/route-resolver'; -import { ApiLayout } from '@/pages/ApiLayout'; -import { ApiPage } from '@/pages/ApiPage'; -import { DocsLayout } from '@/pages/DocsLayout'; -import { DocsPage } from '@/pages/DocsPage'; -import { LandingPage } from '@/pages/LandingPage'; import type { ChronicleConfig } from '@/types'; import { getThemeConfig } from '@/themes/registry'; +const ApiLayout = lazy(() => import('@/pages/ApiLayout').then(m => ({ default: m.ApiLayout }))); +const ApiPage = lazy(() => import('@/pages/ApiPage').then(m => ({ default: m.ApiPage }))); +const DocsLayout = lazy(() => import('@/pages/DocsLayout').then(m => ({ default: m.DocsLayout }))); +const DocsPage = lazy(() => import('@/pages/DocsPage').then(m => ({ default: m.DocsPage }))); +const LandingPage = lazy(() => import('@/pages/LandingPage').then(m => ({ default: m.LandingPage }))); + export function App() { const { pathname } = useLocation(); const { config } = usePageContext(); @@ -35,15 +37,17 @@ export function App() { forcedTheme={themeConfig.forcedTheme} > - {isApi ? ( - - - - ) : ( - - {isLanding ? : } - - )} + + {isApi ? ( + + + + ) : ( + + {isLanding ? : } + + )} + ); } diff --git a/packages/chronicle/src/themes/default/Layout.tsx b/packages/chronicle/src/themes/default/Layout.tsx index 5df8d577..f2ce3d45 100644 --- a/packages/chronicle/src/themes/default/Layout.tsx +++ b/packages/chronicle/src/themes/default/Layout.tsx @@ -9,12 +9,13 @@ import { import { Flex, IconButton, Button, Sidebar } from '@raystack/apsara'; import { PlayIcon } from '@radix-ui/react-icons'; import { cx } from 'class-variance-authority'; -import { useState, useEffect, useMemo, useRef } from 'react'; +import { useState, useEffect, useMemo, useRef, lazy, Suspense } from 'react'; import { Link as RouterLink, useLocation, useNavigate } from 'react-router'; import type { OpenAPIV3 } from 'openapi-types'; import { MethodBadge } from '@/components/api/method-badge'; import { useApiOperation } from '@/lib/use-api-operation'; -import { PlaygroundDialog } from '@/components/api/playground-dialog'; + +const PlaygroundDialog = lazy(() => import('@/components/api/playground-dialog').then(m => ({ default: m.PlaygroundDialog }))); import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher'; import { Search } from '@/components/ui/search'; import { Breadcrumbs } from '@/components/ui/breadcrumbs'; @@ -371,18 +372,22 @@ function TestRequestButton() { > Test request - + {open && ( + + + + )} ); } From cb3c0fc4088d847caf4a1938e89819bcabbd96ab Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 18 May 2026 11:13:44 +0530 Subject: [PATCH 2/6] fix: use skeleton fallback for lazy-loaded page suspense Shows skeleton loader during client-side navigation while lazy chunks load, instead of blank screen. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/App.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/chronicle/src/server/App.tsx b/packages/chronicle/src/server/App.tsx index 14e1953c..f832d7e9 100644 --- a/packages/chronicle/src/server/App.tsx +++ b/packages/chronicle/src/server/App.tsx @@ -8,6 +8,8 @@ import { usePageContext } from '@/lib/page-context'; import { resolveRoute, RouteType } from '@/lib/route-resolver'; import type { ChronicleConfig } from '@/types'; import { getThemeConfig } from '@/themes/registry'; +import { PageSkeleton as DefaultSkeleton } from '@/themes/default/Skeleton'; +import { PageSkeleton as PaperSkeleton } from '@/themes/paper/Skeleton'; const ApiLayout = lazy(() => import('@/pages/ApiLayout').then(m => ({ default: m.ApiLayout }))); const ApiPage = lazy(() => import('@/pages/ApiPage').then(m => ({ default: m.ApiPage }))); @@ -37,7 +39,7 @@ export function App() { forcedTheme={themeConfig.forcedTheme} > - + }> {isApi ? ( @@ -52,6 +54,16 @@ export function App() { ); } +const skeletons: Record = { + default: DefaultSkeleton, + paper: PaperSkeleton, +}; + +function ThemeSkeleton({ name }: { name?: string }) { + const Component = skeletons[name ?? 'default'] ?? DefaultSkeleton; + return ; +} + function RootHead({ config }: { config: ChronicleConfig }) { return ( Date: Mon, 18 May 2026 11:28:03 +0530 Subject: [PATCH 3/6] perf: lazy load TOC component and add skeleton suspense fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lazy load Toc in default theme Page to reduce DocsPage chunk. Inline skeleton fallback for page Suspense boundary. Search lazy-loading skipped — breaks Vite splitting heuristic. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/App.tsx | 24 +++++++++---------- .../chronicle/src/themes/default/Page.tsx | 8 +++++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/chronicle/src/server/App.tsx b/packages/chronicle/src/server/App.tsx index f832d7e9..5476a8c9 100644 --- a/packages/chronicle/src/server/App.tsx +++ b/packages/chronicle/src/server/App.tsx @@ -1,6 +1,6 @@ import '@raystack/apsara/normalize.css'; import '@raystack/apsara/style.css'; -import { ThemeProvider } from '@raystack/apsara'; +import { ThemeProvider, Skeleton, Flex } from '@raystack/apsara'; import { lazy, Suspense } from 'react'; import { Navigate, useLocation } from 'react-router'; import { Head } from '@/lib/head'; @@ -8,8 +8,6 @@ import { usePageContext } from '@/lib/page-context'; import { resolveRoute, RouteType } from '@/lib/route-resolver'; import type { ChronicleConfig } from '@/types'; import { getThemeConfig } from '@/themes/registry'; -import { PageSkeleton as DefaultSkeleton } from '@/themes/default/Skeleton'; -import { PageSkeleton as PaperSkeleton } from '@/themes/paper/Skeleton'; const ApiLayout = lazy(() => import('@/pages/ApiLayout').then(m => ({ default: m.ApiLayout }))); const ApiPage = lazy(() => import('@/pages/ApiPage').then(m => ({ default: m.ApiPage }))); @@ -39,7 +37,7 @@ export function App() { forcedTheme={themeConfig.forcedTheme} > - }> + }> {isApi ? ( @@ -54,14 +52,16 @@ export function App() { ); } -const skeletons: Record = { - default: DefaultSkeleton, - paper: PaperSkeleton, -}; - -function ThemeSkeleton({ name }: { name?: string }) { - const Component = skeletons[name ?? 'default'] ?? DefaultSkeleton; - return ; +function PageFallback() { + return ( + + + + {[...new Array(12)].map((_, i) => ( + + ))} + + ); } function RootHead({ config }: { config: ChronicleConfig }) { diff --git a/packages/chronicle/src/themes/default/Page.tsx b/packages/chronicle/src/themes/default/Page.tsx index a1bbaad7..a3d69186 100644 --- a/packages/chronicle/src/themes/default/Page.tsx +++ b/packages/chronicle/src/themes/default/Page.tsx @@ -1,9 +1,11 @@ 'use client'; import { Flex, Headline } from '@raystack/apsara'; +import { lazy, Suspense } from 'react'; import type { ThemePageProps } from '@/types'; import styles from './Page.module.css'; -import { Toc } from './Toc'; + +const Toc = lazy(() => import('./Toc').then(m => ({ default: m.Toc }))); export function Page({ page }: ThemePageProps) { return ( @@ -16,7 +18,9 @@ export function Page({ page }: ThemePageProps) { )}
{page.content}
- + + + ); } From 1160a3443fd894cc10236dce1fd3e90812238876 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 18 May 2026 11:36:34 +0530 Subject: [PATCH 4/6] refactor: replace inline styles with CSS module in PageFallback Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/App.module.css | 4 ++++ packages/chronicle/src/server/App.tsx | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 packages/chronicle/src/server/App.module.css diff --git a/packages/chronicle/src/server/App.module.css b/packages/chronicle/src/server/App.module.css new file mode 100644 index 00000000..9ea66e61 --- /dev/null +++ b/packages/chronicle/src/server/App.module.css @@ -0,0 +1,4 @@ +.fallback { + padding: var(--rs-space-8); + max-width: 768px; +} diff --git a/packages/chronicle/src/server/App.tsx b/packages/chronicle/src/server/App.tsx index 5476a8c9..cfa7fc23 100644 --- a/packages/chronicle/src/server/App.tsx +++ b/packages/chronicle/src/server/App.tsx @@ -8,6 +8,7 @@ import { usePageContext } from '@/lib/page-context'; import { resolveRoute, RouteType } from '@/lib/route-resolver'; import type { ChronicleConfig } from '@/types'; import { getThemeConfig } from '@/themes/registry'; +import styles from './App.module.css'; const ApiLayout = lazy(() => import('@/pages/ApiLayout').then(m => ({ default: m.ApiLayout }))); const ApiPage = lazy(() => import('@/pages/ApiPage').then(m => ({ default: m.ApiPage }))); @@ -54,7 +55,7 @@ export function App() { function PageFallback() { return ( - + {[...new Array(12)].map((_, i) => ( From cfa2616b72e9fc766e4c4cf6dc720360ebf47842 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 18 May 2026 11:42:50 +0530 Subject: [PATCH 5/6] fix: use unique keys for API sidebar entries Multiple APIs can share the same basePath, causing duplicate key warning. Use basePath + name for unique keys. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/themes/default/Layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/chronicle/src/themes/default/Layout.tsx b/packages/chronicle/src/themes/default/Layout.tsx index f2ce3d45..5a7ddc4c 100644 --- a/packages/chronicle/src/themes/default/Layout.tsx +++ b/packages/chronicle/src/themes/default/Layout.tsx @@ -145,7 +145,7 @@ export function Layout({ ))} {apiEntries.map(api => ( Date: Mon, 18 May 2026 11:44:37 +0530 Subject: [PATCH 6/6] fix: use 80% width for page fallback skeleton Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/App.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/chronicle/src/server/App.module.css b/packages/chronicle/src/server/App.module.css index 9ea66e61..0864892e 100644 --- a/packages/chronicle/src/server/App.module.css +++ b/packages/chronicle/src/server/App.module.css @@ -1,4 +1,4 @@ .fallback { padding: var(--rs-space-8); - max-width: 768px; + width: 80%; }