| name | react-vite-performance |
| description | |
| tier | standard React + Vite performance optimization — code splitting, lazy loading, bundle analysis, Web Vitals, and modern React patterns for fast web applications |
| applyTo | **/*react*,**/*vite*,**/vite.config*,**/*bundle*,**/*lazy*,**/*performance* |
React + Vite Performance Optimization
Fast by default, optimized by design — sub-300KB bundles and sub-2s load times.
Performance Targets
| Metric | Target | Tool |
|---|
| Initial JS (gzipped) | < 300 KB | rollup-plugin-visualizer |
| First Contentful Paint | < 1.5s | Lighthouse |
| Time to Interactive | < 3s | Lighthouse |
| Largest Contentful Paint | < 2.5s | Web Vitals |
| Cumulative Layout Shift | < 0.1 | Web Vitals |
Vite Build Configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
target: 'esnext',
minify: 'esbuild',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui': ['@headlessui/react', '@heroicons/react'],
},
},
},
chunkSizeWarningLimit: 500,
},
optimizeDeps: {
include: ['react', 'react-dom', 'react-router-dom'],
},
});
Bundle Analysis
npm install -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [
react(),
visualizer({ filename: 'dist/stats.html', gzipSize: true }),
]
Code Splitting
Route-Based Splitting
import { lazy, Suspense } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{ path: 'dashboard', element: <Dashboard /> },
{ path: 'settings', element: <Settings /> },
],
},
]);
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<RouterProvider router={router} />
</Suspense>
);
}
Component-Based Splitting
const Chart = lazy(() => import('./components/Chart'));
function Dashboard() {
return (
<Suspense fallback={<ChartSkeleton />}>
<Chart data={data} />
</Suspense>
);
}
Preloading on Hover
function NavLink({ to, loader, children }) {
const preload = () => loader();
return (
<Link to={to} onMouseEnter={preload} onFocus={preload}>
{children}
</Link>
);
}
Modern React Patterns
Compiler-Friendly Code (React 19+)
React 19's compiler auto-memoizes. Write straightforward components:
function UserCard({ user }: { user: User }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
const UserCard = React.memo(({ user }) => { ... });
use() Hook for Data Loading
import { use, Suspense } from 'react';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
function App() {
const userPromise = fetchUser(userId);
return (
<Suspense fallback={<UserSkeleton />}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
useTransition for Non-Urgent Updates
import { useState, useTransition } from 'react';
function SearchableList({ items }: { items: Item[] }) {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
const handleSearch = (value: string) => {
setQuery(value);
startTransition(() => {
setFiltered(items.filter(item =>
item.name.toLowerCase().includes(value.toLowerCase())
));
});
};
return (
<>
<input value={query} onChange={e => handleSearch(e.target.value)} />
{isPending && <Spinner />}
<List items={filtered} />
</>
);
}
TanStack Query Optimization
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
refetchOnWindowFocus: false,
retry: 1,
},
},
});
Optimistic Updates
const updateUser = useMutation({
mutationFn: (data: UserUpdate) => api.updateUser(data),
onMutate: async (newData) => {
await queryClient.cancelQueries({ queryKey: ['user'] });
const previous = queryClient.getQueryData(['user']);
queryClient.setQueryData(['user'], old => ({ ...old, ...newData }));
return { previous };
},
onError: (_err, _new, context) => {
queryClient.setQueryData(['user'], context?.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
});
Asset Optimization
Images
<img src={src} alt={alt} loading="lazy" decoding="async" fetchpriority="low" />
<img src={src} alt={alt} fetchpriority="high" />
Fonts
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-var.woff2') format('woff2-variations');
font-display: swap;
font-weight: 100 900;
}
<link rel="preload" href="/fonts/Inter-var.woff2" as="font" type="font/woff2" crossorigin>
Web Vitals Monitoring
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
function reportMetric(metric: Metric) {
analytics.track('web-vitals', {
name: metric.name,
value: metric.value,
rating: metric.rating,
});
}
onCLS(reportMetric);
onFID(reportMetric);
onLCP(reportMetric);
onFCP(reportMetric);
onTTFB(reportMetric);
Performance Checklist
Build
Runtime
Assets