Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
fabioc-aloha avatar

React Vite Performance

  • 1 installs
  • 3 repo stars
  • Updated August 5, 2026
  • fabioc-aloha/alex_skill_mall

Optimizes React + Vite apps for speed via code splitting, lazy loading, bundle analysis, and Web Vitals targets like sub-300KB bundles.

About

Covers React + Vite performance work: code splitting, lazy loading, bundle analysis, and Web Vitals against concrete targets. Frontend developers use it to hit sub-300KB bundles and sub-2s load times on React 19 / Vite 6.

  • Targets sub-300KB gzipped JS and sub-2s loads
  • Bundle analysis with rollup-plugin-visualizer and Lighthouse

React Vite Performance by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #1,912 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fabioc-aloha/alex_skill_mall --skill react-vite-performance

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars3
Last updatedAugust 5, 2026
Repositoryfabioc-aloha/alex_skill_mall

What it does

Optimizes React + Vite apps for speed via code splitting, lazy loading, bundle analysis, and Web Vitals targets like sub-300KB bundles.

Files

SKILL.mdMarkdownGitHub ↗

React + Vite Performance Optimization

Fast by default, optimized by design — sub-300KB bundles and sub-2s load times.

>

Targets: React 19+ / Vite 6+ | Last validated: April 2026

Performance Targets

MetricTargetTool
Initial JS (gzipped)< 300 KBrollup-plugin-visualizer
First Contentful Paint< 1.5sLighthouse
Time to Interactive< 3sLighthouse
Largest Contentful Paint< 2.5sWeb Vitals
Cumulative Layout Shift< 0.1Web 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'],
          // Group large dependencies into separate chunks
        },
      },
    },
    chunkSizeWarningLimit: 500,
  },
  optimizeDeps: {
    include: ['react', 'react-dom', 'react-router-dom'],
  },
});

Bundle Analysis

npm install -D rollup-plugin-visualizer
// vite.config.ts
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(); // e.g., () => import('./pages/Settings')

  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:

// ✅ Let the compiler optimize
function UserCard({ user }: { user: User }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// ❌ Avoid manual memoization the compiler handles
const UserCard = React.memo(({ user }) => { ... });

Vite setup — install babel-plugin-react-compiler and add to vite.config.ts:

// vite.config.ts
import react from '@vitejs/plugin-react';
export default defineConfig({
  plugins: [
    react({ babel: { plugins: ['babel-plugin-react-compiler'] } }),
  ],
});

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); // Urgent: update input immediately
    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,    // 5 minutes
      gcTime: 30 * 60 * 1000,      // 30 minutes
      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

// Non-critical images: lazy load
<img src={src} alt={alt} loading="lazy" decoding="async" fetchpriority="low" />

// LCP image: load immediately
<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

INP replaced FID as a Core Web Vital in March 2024. INP measures overall responsiveness (all interactions), not just the first one.
import { onCLS, onINP, 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);
onINP(reportMetric);   // Interaction to Next Paint (replaced FID)
onLCP(reportMetric);
onFCP(reportMetric);
onTTFB(reportMetric);

Performance Checklist

Build

  • [ ] Manual chunks for large vendor libs
  • [ ] Tree shaking enabled (ESM imports)
  • [ ] Dependency pre-bundling configured
  • [ ] Bundle size tracked in CI

Runtime

  • [ ] Routes lazy-loaded with Suspense
  • [ ] Heavy components split into separate chunks
  • [ ] useTransition for non-urgent state updates
  • [ ] Virtual scrolling for long lists
  • [ ] Debounce expensive operations

Assets

  • [ ] Images: WebP, lazy loading, responsive sizes
  • [ ] Fonts: font-display: swap, preload critical
  • [ ] Brotli/gzip compression enabled
  • [ ] Critical CSS inlined

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.