diff --git a/apps/web/src/components/blog/SkillDifficultyCharts.tsx b/apps/web/src/components/blog/SkillDifficultyCharts.tsx
index d67dd476..02b0e64e 100644
--- a/apps/web/src/components/blog/SkillDifficultyCharts.tsx
+++ b/apps/web/src/components/blog/SkillDifficultyCharts.tsx
@@ -1,8 +1,14 @@
'use client'
+import dynamic from 'next/dynamic'
import { useState, useEffect } from 'react'
-import ReactECharts from 'echarts-for-react'
import * as Tabs from '@radix-ui/react-tabs'
+
+// Dynamic import echarts to reduce bundle size
+const ReactECharts = dynamic(() => import('echarts-for-react'), {
+ ssr: false,
+ loading: () =>
Loading chart...
,
+})
import { css } from '../../../styled-system/css'
interface SkillData {
diff --git a/apps/web/src/components/blog/ValidationCharts.tsx b/apps/web/src/components/blog/ValidationCharts.tsx
index 68b6b144..09a19fad 100644
--- a/apps/web/src/components/blog/ValidationCharts.tsx
+++ b/apps/web/src/components/blog/ValidationCharts.tsx
@@ -1,8 +1,14 @@
'use client'
import * as Tabs from '@radix-ui/react-tabs'
-import ReactECharts from 'echarts-for-react'
+import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'
+
+// Dynamic import echarts to reduce bundle size
+const ReactECharts = dynamic(() => import('echarts-for-react'), {
+ ssr: false,
+ loading: () => Loading chart...
,
+})
import { css } from '../../../styled-system/css'
const chartContainerStyles = css({
diff --git a/apps/web/src/components/nav/CreateRoomModal.tsx b/apps/web/src/components/nav/CreateRoomModal.tsx
index a1a25f84..d71c1051 100644
--- a/apps/web/src/components/nav/CreateRoomModal.tsx
+++ b/apps/web/src/components/nav/CreateRoomModal.tsx
@@ -1,10 +1,10 @@
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import * as Select from '@radix-ui/react-select'
import { animated } from '@react-spring/web'
import { Modal } from '@/components/common/Modal'
import { useCreateRoom, useRoomData } from '@/hooks/useRoomData'
-import { getAvailableGames } from '@/lib/arcade/game-registry'
import { RoomShareButtons } from './RoomShareButtons'
+import type { GameDefinition } from '@/lib/arcade/game-sdk/types'
export interface CreateRoomModalProps {
/**
@@ -31,8 +31,17 @@ type ModalState = 'creating' | 'created'
export function CreateRoomModal({ isOpen, onClose, onSuccess }: CreateRoomModalProps) {
const { mutateAsync: createRoom, isPending } = useCreateRoom()
const { getRoomShareUrl } = useRoomData()
- const availableGames = getAvailableGames()
+ const [availableGames, setAvailableGames] = useState[]>([])
const [error, setError] = useState('')
+
+ // Lazy load game registry only when modal opens
+ useEffect(() => {
+ if (isOpen && availableGames.length === 0) {
+ import('@/lib/arcade/game-registry').then(({ getAvailableGames }) => {
+ setAvailableGames(getAvailableGames())
+ })
+ }
+ }, [isOpen, availableGames.length])
const [gameName, setGameName] = useState('__choose_later__') // Special value = user will choose later
const [accessMode, setAccessMode] = useState<
'open' | 'password' | 'approval-only' | 'restricted'
diff --git a/apps/web/src/components/practice/SkillProgressChart.tsx b/apps/web/src/components/practice/SkillProgressChart.tsx
index 08c47343..09bc74c1 100644
--- a/apps/web/src/components/practice/SkillProgressChart.tsx
+++ b/apps/web/src/components/practice/SkillProgressChart.tsx
@@ -1,7 +1,13 @@
'use client'
-import ReactECharts from 'echarts-for-react'
+import dynamic from 'next/dynamic'
import { useCallback, useMemo, useState } from 'react'
+
+// Dynamic import echarts to avoid bundling 58MB library on pages that don't use charts
+const ReactECharts = dynamic(() => import('echarts-for-react'), {
+ ssr: false,
+ loading: () => Loading chart...
,
+})
import {
getExtendedClassification,
type ExtendedSkillClassification,