Реализация Code Splitting по маршрутам для веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Code Splitting по маршрутам для веб-приложения
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Code Splitting по маршрутам для веб-приложения

Code splitting по маршрутам — самый эффективный вид разбивки бандла. Вместо одного монолитного JS-файла браузер загружает только код текущей страницы. Переход на другой маршрут подгружает соответствующий чанк. Итог: меньше трафика, быстрее первый рендер, лучше LCP и TTI в Core Web Vitals.

Проблема без code splitting

Типичное SPA без разбивки:

dist/
  assets/
    index-Bx7K9m2p.js   # 1.2 MB — весь код приложения
    vendor-Dq8R3nYk.js  # 800 KB — все зависимости

Пользователь, открывший главную страницу, скачивает код страницы оформления заказа, панели администратора и всех других разделов — хотя они ему не нужны прямо сейчас.

React Router v6 + React.lazy

// router/index.tsx
import { lazy, Suspense } from 'react'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { AppLayout } from '@/layouts/AppLayout'
import { PageLoader } from '@/components/PageLoader'

// Каждый маршрут — отдельный чанк
const Home        = lazy(() => import('@/pages/Home'))
const Catalog     = lazy(() => import('@/pages/Catalog'))
const ProductPage = lazy(() => import('@/pages/ProductPage'))
const Cart        = lazy(() => import('@/pages/Cart'))
const Checkout    = lazy(() => import('@/pages/Checkout'))
const Account     = lazy(() => import('@/pages/Account'))
const AdminDashboard = lazy(() => import('@/pages/admin/Dashboard'))

const router = createBrowserRouter([
  {
    path: '/',
    element: <AppLayout />,
    children: [
      { index: true, element: <Home /> },
      { path: 'catalog',            element: <Catalog /> },
      { path: 'catalog/:id',        element: <ProductPage /> },
      { path: 'cart',               element: <Cart /> },
      { path: 'checkout',           element: <Checkout /> },
      { path: 'account/*',          element: <Account /> },
    ],
  },
  {
    path: '/admin',
    element: <AdminDashboard />,
  },
])

// Suspense оборачивает RouterProvider — один на всё приложение
export function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <RouterProvider router={router} />
    </Suspense>
  )
}

Vite автоматически создаёт отдельный чанк для каждого динамического импорта:

dist/assets/
  Home-C2mK8pQr.js        # 45 KB
  Catalog-Fp7Xd3nY.js     # 92 KB
  ProductPage-Bv2Rs9mL.js # 67 KB
  Checkout-Dq4Wt1kJ.js    # 180 KB (Stripe, форм много)
  AdminDashboard-Xk9Pn3rT.js # 340 KB (charts, tables)
  vendor-Ym3Cx8wQ.js      # 800 KB (общие зависимости)

Именование чанков через магические комментарии

const Checkout = lazy(() =>
  import(/* webpackChunkName: "checkout" */ '@/pages/Checkout')
)

// В Vite — через rollupOptions
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // vendor чанки по категориям
          'vendor-react':  ['react', 'react-dom', 'react-router-dom'],
          'vendor-ui':     ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
          'vendor-forms':  ['react-hook-form', 'zod'],
          'vendor-charts': ['recharts'],
        },
      },
    },
  },
})

Next.js App Router

В App Router code splitting работает по умолчанию: каждый page.tsx — отдельный сегмент. Дополнительно используем dynamic() для тяжёлых компонентов внутри страниц:

// app/catalog/page.tsx
import dynamic from 'next/dynamic'
import { ProductGrid } from '@/components/ProductGrid'

// Фильтры — тяжёлые, рендерятся ниже fold
const FilterPanel = dynamic(() => import('@/components/FilterPanel'), {
  loading: () => <FilterSkeleton />,
})

// Карта магазинов — только client-side
const StoreMap = dynamic(() => import('@/components/StoreMap'), {
  ssr: false,
  loading: () => <div style={{ height: 400 }} className="bg-muted animate-pulse" />,
})

export default function CatalogPage() {
  return (
    <div className="flex gap-8">
      <FilterPanel />
      <main>
        <ProductGrid />
        <StoreMap />
      </main>
    </div>
  )
}

Prefetching маршрутов

Загружаем чанк заранее — до того, как пользователь перешёл по ссылке:

// При hover на ссылку
import { useEffect } from 'react'

function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
  const prefetch = () => {
    // Динамически импортируем соответствующий чанк
    if (to === '/catalog') import('@/pages/Catalog')
    if (to === '/checkout') import('@/pages/Checkout')
  }

  return (
    <Link to={to} onMouseEnter={prefetch} onFocus={prefetch}>
      {children}
    </Link>
  )
}

В Next.js <Link> делает prefetch автоматически для видимых ссылок в production. Управление:

<Link href="/checkout" prefetch={false}>  {/* отключить */}
<Link href="/admin" prefetch={true}>       {/* принудительно */}

Loading states и error boundaries

// components/PageLoader.tsx
export function PageLoader() {
  return (
    <div className="flex items-center justify-center min-h-[60vh]">
      <div className="flex flex-col items-center gap-4">
        <Spinner size="lg" />
        <p className="text-muted-foreground text-sm">Загрузка страницы…</p>
      </div>
    </div>
  )
}
// ErrorBoundary для чанков, которые не загрузились (нет сети, 404 чанка)
class ChunkErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  handleRetry = () => {
    this.setState({ hasError: false })
    window.location.reload()
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="text-center py-16">
          <p>Не удалось загрузить страницу</p>
          <button onClick={this.handleRetry}>Обновить</button>
        </div>
      )
    }
    return this.props.children
  }
}

// Использование
<ChunkErrorBoundary>
  <Suspense fallback={<PageLoader />}>
    <RouterProvider router={router} />
  </Suspense>
</ChunkErrorBoundary>

Анализ результатов

# Измерить размеры чанков до и после
npx vite build --reporter=verbose

# Source map explorer
npm install --save-dev source-map-explorer
npx source-map-explorer dist/assets/*.js

# Bundle analyzer
npm install --save-dev rollup-plugin-visualizer

Целевые показатели: initial bundle < 200 KB gzipped, каждый route chunk < 100 KB gzipped.

Сроки

Настройка code splitting для существующего роутера — 1 день. Вместе с анализом бандла, ручным разбитием vendor-чанков, настройкой prefetch и error boundary — 2–3 дня.