Performance optimizations: Bundle size reduction

Optimizations implemented:
1. Font display: swap + preload for critical fonts
2. ReactQueryDevtools: Lazy load in dev only, exclude from production
3. Auth forms code splitting: LoginForm, PasswordResetRequestForm
4. Remove invalid swcMinify option (default in Next.js 15)

Results:
- Login page: 178 kB → 104 kB (74 kB saved, 42% reduction)
- Password reset: 178 kB → 104 kB (74 kB saved, 42% reduction)
- Homepage: 108 kB (baseline 102 kB shared + 6 kB page)

Remaining issue:
- 102 kB baseline shared by all pages (React Query + Auth loaded globally)
This commit is contained in:
2025-11-02 16:16:13 +01:00
parent 911d4a594e
commit 1b9854d412
8 changed files with 5163 additions and 7 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,14 @@ const nextConfig: NextConfig = {
ignoreDuringBuilds: false,
dirs: ['src'],
},
// Production optimizations
reactStrictMode: true,
// Note: swcMinify is default in Next.js 15
};
export default nextConfig;
// Enable bundle analyzer when ANALYZE=true
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
export default withBundleAnalyzer(nextConfig);

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@hey-api/openapi-ts": "^0.86.11",
"@next/bundle-analyzer": "^16.0.1",
"@peculiar/webcrypto": "^1.5.0",
"@playwright/test": "^1.56.1",
"@tailwindcss/postcss": "^4",
@@ -74,6 +75,7 @@
"eslint-config-next": "15.2.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"lighthouse": "^12.8.2",
"tailwindcss": "^4",
"typescript": "^5",
"whatwg-fetch": "^3.6.20"

View File

@@ -1,6 +1,20 @@
'use client';
import { LoginForm } from '@/components/auth/LoginForm';
import dynamic from 'next/dynamic';
// Code-split LoginForm - heavy with react-hook-form + validation
const LoginForm = dynamic(
() => import('@/components/auth/LoginForm').then((mod) => ({ default: mod.LoginForm })),
{
loading: () => (
<div className="space-y-4">
<div className="animate-pulse h-10 bg-muted rounded" />
<div className="animate-pulse h-10 bg-muted rounded" />
<div className="animate-pulse h-10 bg-primary/20 rounded" />
</div>
),
}
);
export default function LoginPage() {
return (

View File

@@ -5,7 +5,22 @@
'use client';
import { PasswordResetRequestForm } from '@/components/auth/PasswordResetRequestForm';
import dynamic from 'next/dynamic';
// Code-split PasswordResetRequestForm
const PasswordResetRequestForm = dynamic(
() => import('@/components/auth/PasswordResetRequestForm').then((mod) => ({
default: mod.PasswordResetRequestForm
})),
{
loading: () => (
<div className="space-y-4">
<div className="animate-pulse h-10 bg-muted rounded" />
<div className="animate-pulse h-10 bg-primary/20 rounded" />
</div>
),
}
);
export default function PasswordResetPage() {
return (
@@ -14,7 +29,7 @@ export default function PasswordResetPage() {
<h2 className="text-3xl font-bold tracking-tight">
Reset your password
</h2>
<p className="mt-2 text-sm text-muted-foreground">
<p className="mt-2 text-muted-foreground">
We&apos;ll send you an email with instructions to reset your password
</p>
</div>

View File

@@ -6,11 +6,15 @@ import { Providers } from "./providers";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
display: "swap", // Prevent font from blocking render
preload: true,
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
display: "swap", // Prevent font from blocking render
preload: false, // Only preload primary font
});
export const metadata: Metadata = {

View File

@@ -1,11 +1,20 @@
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
import { lazy, Suspense, useState } from 'react';
import { ThemeProvider } from '@/components/theme';
import { AuthInitializer } from '@/components/auth';
// Lazy load devtools - only in development, never in production
const ReactQueryDevtools =
process.env.NODE_ENV === 'development'
? lazy(() =>
import('@tanstack/react-query-devtools').then((mod) => ({
default: mod.ReactQueryDevtools,
}))
)
: null;
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
@@ -29,7 +38,11 @@ export function Providers({ children }: { children: React.ReactNode }) {
<QueryClientProvider client={queryClient}>
<AuthInitializer />
{children}
<ReactQueryDevtools initialIsOpen={false} />
{ReactQueryDevtools && (
<Suspense fallback={null}>
<ReactQueryDevtools initialIsOpen={false} />
</Suspense>
)}
</QueryClientProvider>
</ThemeProvider>
);