Skip to content

[perf] Lazy-load ChallengeAuthoring on /challenge/create route#102

Draft
github-actions[bot] wants to merge 1 commit intomainfrom
perf/lazy-load-challenge-authoring-d859009d7badb3ff
Draft

[perf] Lazy-load ChallengeAuthoring on /challenge/create route#102
github-actions[bot] wants to merge 1 commit intomainfrom
perf/lazy-load-challenge-authoring-d859009d7badb3ff

Conversation

@github-actions
Copy link
Copy Markdown

Tier 3 — Loading Optimisation: Code Splitting via next/dynamic

Baseline

The /challenge/create route statically imported ChallengeAuthoring, bundling all 1 005 lines (across 5 files) into the route's initial JS chunk:

File Lines
authoring-chat.tsx 334
quick-templates.tsx 204
index.tsx 146
authoring-message-list.tsx 141
authoring-input-form.tsx 66
Total 1 005

All of this code was downloaded and parsed before the user could interact with the page header or see any UI.

Fix

Replace the static import with next/dynamic({ ssr: false }):

// Before
import { ChallengeAuthoring } from '@/components/ChallengeAuthoring';

// After
const ChallengeAuthoring = dynamic(
  () => import('@/components/ChallengeAuthoring').then((mod) => mod.ChallengeAuthoring),
  { ssr: false, loading: () => (div className={styles.loading})(Spinner size="medium" /)(/div) }
);
```

- `ssr: false`  the page is already `'use client'`; no server-side rendering needed for this component.
- `loading` fallback  reuses the existing `.loading` CSS class (centred spinner), so the layout doesn't shift.
- The `AppHeader`, error `Banner`, and all hooks load instantly in the initial chunk; the authoring UI streams in as a separate chunk when the browser is ready.

Also replaced the one remaining `style=\{\{ flex: 1, overflow: 'hidden' }}` on the wrapper `(div)` with a new `.authoringWrapper` CSS class in `challenge.module.css`, keeping the file inline-style-free.

### Result

`ChallengeAuthoring` and its sub-components are now a separate JS chunk that loads after the critical path, reducing Time-to-Interactive for `/challenge/create`.

### Test update

`create-page.test.ts` was updated to use `await act(async () => ...)` in `renderPage()`. This is required because `next/dynamic` wraps the import in `React.lazy`, which resolves asynchronously even when the module is mocked synchronously by Vitest. Awaiting `act` flushes the microtask queue so the lazy boundary resolves before assertions run. All 3 tests in the file continue to pass.

### Verification

```
npx tsc --noEmit    clean (0 errors)
npm run lint        clean (0 warnings)
npm test            1 025 passed (3 pre-existing GitHub-auth failures unrelated to this change)

Generated by Daily Performance Improver

Use next/dynamic({ ssr: false }) to split ChallengeAuthoring (1005 lines
across 5 files) from the /challenge/create initial JS bundle. AppHeader,
error Banner, and hooks load instantly; heavy authoring UI streams in
as a separate chunk, reducing Time-to-Interactive for the route.

Also converts the static inline style on the wrapper div
(flex: 1; overflow: hidden) to an .authoringWrapper CSS class.

Test: renderPage() is now async (await act(async () => ...)) so the
React.lazy boundary resolves the mocked import before assertions run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Author

Pull request created: #102

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants