Kamil Owczarek
Opublikowano

92% mniejszy bundle: jak inteligentne ładowanie tłumaczeń odmieniło naszą międzynarodową stronę

Autorzy

92% mniejszy bundle: jak inteligentne ładowanie tłumaczeń odmieniło naszą międzynarodową stronę

Problem: gdy „wsparcie dla wielu języków” zaczyna zabijać wydajność

Nasza platforma obsługuje 11 międzynarodowych rynków (polski, angielski, niemiecki, ukraiński, rosyjski, węgierski, rumuński, francuski, słoweński, hiszpański i włoski). To, co miało być przewagą konkurencyjną, po cichu rujnowało nam user experience.

Brutalna prawda: każdy odwiedzający musiał pobrać 1 MB danych z tłumaczeniami (a ta liczba rosła z dnia na dzień), zanim zobaczył cokolwiek sensownego. Zmuszaliśmy ludzi do pobierania tłumaczeń panelu admina, którego nigdy nie zobaczą, stron produktowych, na które być może nigdy nie wejdą, i komunikatów błędów dla scenariuszy, które prawie się nie zdarzają.

Szokująca zmiana: z czerwonego na zielone w jednym ruchu

Przed: katastrofa wydajnościowa

  • Lighthouse Performance: głęboka czerwień
  • First Contentful Paint: 4,8 sekundy
  • Time to Interactive: 6,2 sekundy
  • Początkowy bundle tłumaczeń: 1 MB
  • Wydajność na mobile: katastrofalna

Po: zielone ptaszki od Google'a

  • Lighthouse Performance: soczysta zieleń we wszystkich metrykach
  • First Contentful Paint: 2,5 sekundy (48% szybciej)
  • Time to Interactive: 3,3 sekundy (47% poprawy)
  • Początkowy bundle tłumaczeń: 80 KB (92% mniej)
  • Wydajność na mobile: znakomita

Rozwiązanie warte 92%: ładuj to, czego potrzebujesz, wtedy gdy tego potrzebujesz

Zamiast zmuszać każdego odwiedzającego do pobierania tłumaczeń na każdą możliwą okoliczność, wdrożyliśmy inteligentny lazy loading: na start dostarczamy wyłącznie tłumaczenia niezbędne, a chunki przypisane do konkretnych stron dociągamy na żądanie.

Architektura techniczna

1. Sprytna konfiguracja Nuxt i18n

// nuxt.config.ts
export default defineNuxtConfig({
  i18n: {
    locales: [
      {
        code: 'pl',
        language: 'pl',
        file: 'translation.ts',
        name: 'Polski',
      },
      {
        code: 'en',
        language: 'en',
        file: 'translation.ts',
        name: 'English',
      },
      // ... 9 more languages
    ],
    lazy: true, // 🚀 GAME CHANGER: 92% bundle reduction
    langDir: 'lang',
    defaultLocale: 'pl',
    strategy: 'prefix_except_default',
    vueI18n: './i18n.config.ts',
  },
})

2. Inteligentne ładowanie początkowe

// lang/translation.ts
export default defineI18nLocale((locale) => {
  const nuxt = useNuxtApp()
  nuxt._vueI18n.__firstAccess = false

  // Load only core translations (80KB vs previous 1MB)
  return $fetch(`/api/public/locales/local?lang=${locale}`, {
    method: 'get',
    cache: 'no-store',
  })
})

3. Doładowywanie chunków na żądanie

// composables/useLocalesLoader.ts
export const useLocalesLoader = async (startsWith: string) => {
  const { locale, setLocaleMessage, messages } = useCustomI18n()

  const { data } = await useCustomQuery(`/api/public/locales/${startsWith}`, { startsWith })

  watch(
    [data],
    ([newData]) => {
      if (newData) {
        // Merge additional translations seamlessly
        setLocaleMessage(locale.value, {
          ...messages.value[locale.value],
          ...newData,
        })
      }
    },
    { immediate: true }
  )
}

4. Ładowanie tłumaczeń pod konkretną stronę

<!-- pages/collections/[collectionName]/magnetic.vue -->
<script setup lang="ts">
// Load only this page's translations (5-15KB additional)
await useLocalesLoader('landing.magnetic')

const { t } = useCustomI18n()

useSeoMeta({
  title: () => t('landing.magnetic.seo.title'),
  description: () => t('landing.magnetic.seo.description'),
})
</script>

Strategia optymalizacji po stronie backendu

// server/api/public/locales/[startsWith].get.ts
export default defineCustomCacheEventHandler(async (event) => {
  const { startsWith } = await getValidatedRouterParams(event, getLocaleParams.parse)
  const lang = getLocale(event)
  const select = getLocalizedSelect(lang)

  // Targeted queries: only fetch what's needed
  const data = await prisma.dictionaryEntry.findMany({
    select: {
      id: true,
      translations: { select },
    },
    where: {
      id: { startsWith }, // 🎯 Precision loading
    },
  })

  return data.reduce(
    (prev, { id, translations }) => ({
      ...prev,
      [id]: translations[lang] ?? id,
    }),
    {}
  )
})

Strategiczna organizacja tłumaczeń

Przebudowaliśmy architekturę tłumaczeń wokół przemyślanych prefiksów:

// Strategic chunking for maximum efficiency
const translationStrategy = {
  // Core translations (80KB) - loaded immediately
  'local.navigation': 'Navigation menus and basic UI',
  'local.common': 'Buttons, forms, essential interactions',
  'local.seo': 'Critical SEO meta tags and descriptions',

  // Page-specific chunks (5-15KB each) - loaded on demand
  'landing.magnetic': 'Magnetic collection landing page',
  'landing.thermostat': 'Thermostat product showcase',
  dashboard: 'Admin interfaces (rarely accessed)',
}

Rewolucja w wydajności: liczby nie kłamią

Zmiana rozmiaru bundle'a:

  • Początkowe pobranie: 92% mniej (1 MB → 80 KB)
  • Chunki per strona: 5-15 KB dociągane w razie potrzeby
  • Łączna optymalizacja: 920 KB zaoszczędzone przy pierwszym ładowaniu

Przyspieszenie:

  • First Contentful Paint: 48% szybciej (4,8 s → 2,5 s)
  • Time to Interactive: 47% poprawy (6,2 s → 3,3 s)
  • Largest Contentful Paint: 61% lepszy wynik
  • Czasy ładowania na mobile: 58% poprawy na wszystkich urządzeniach

Zyski po stronie użytkownika:

  • Spadek bounce rate: 28% poprawy na stronach międzynarodowych
  • Konwersja na mobile: 35% więcej transakcji mobilnych
  • Oceny satysfakcji: 43% poprawy w feedbacku
  • Core Web Vitals: komplet zielonych wyników we wszystkich metrykach

Szczegóły implementacji

Architektura bazy pod ładowanie chunkami:

model DictionaryEntry {
  id            String      @id // "landing.magnetic.seo.title"
  translationId Int
  translations  Translation @relation(fields: [translationId])
}

model Translation {
  id Int @id @default(autoincrement())
  pl String? // Polish
  en String? // English
  de String? // German
  uk String? // Ukrainian
  ru String? // Russian
  hu String? // Hungarian
  ro String? // Romanian
  fr String? // French
  sl String? // Slovenian
  es String? // Spanish
  it String? // Italian
}

Wsparcie tłumaczeń po stronie serwera:

export const useServerTranslation = async (event: H3Event, startsWith: string) => {
  const lang = getLocale(event)
  const select = getLocalizedSelect(lang)

  const response = await prisma.dictionaryEntry.findMany({
    select: { id: true, translations: { select } },
    where: { id: { startsWith } },
  })

  const localesMap = new Map()
  response.forEach((option) => {
    localesMap.set(option.id, option.translations[lang] ?? null)
  })

  return (key: string) => localesMap.get(key) ?? key
}

Routing międzynarodowy zoptymalizowany pod SEO:

defineI18nRoute({
  paths: {
    pl: '/kolekcja/magnetic',
    en: '/collection/magnetic',
    de: '/kollektion/magnetic',
    ru: '/kollektsiya/magnetic',
    hu: '/gyujtemeny/magnetic',
    ro: '/colectie/magnetic',
    uk: '/kolektsiya/magnetic',
    fr: '/collection/magnetic',
    sl: '/zbirka/magnetic',
    it: '/collezione/magnetic',
    es: '/coleccion/magnetic',
  },
})

Lepszy developer experience

Narzędzia do zarządzania tłumaczeniami:

  • Import/eksport z Excela: masowe zarządzanie tłumaczeniami
  • Wykrywanie brakujących tłumaczeń: automatyczna kontrola jakości
  • Podgląd w czasie rzeczywistym: natychmiastowy efekt zmian
  • Historia tłumaczeń: pełne śledzenie zmian
  • Monitoring wydajności: alerty o rozmiarze bundle'a i podpowiedzi optymalizacyjne

Strategia fallbacku:

// Graceful degradation when translations are missing
return data.reduce(
  (prev, { id, translations }) => ({
    ...prev,
    [id]: translations[lang] ?? id, // Fallback to key name
  }),
  {}
)

Wpływ na biznes: nie tylko wydajność

Zmiana w SEO:

  • Mobile-first indexing: pełna zgodność
  • Core Web Vitals: zieleń na wszystkich 11 rynkach
  • Sygnały Page Experience: dramatyczna poprawa
  • Pozycje w wyszukiwarce: widoczny wzrost we wszystkich regionach

Korzyści infrastrukturalne:

  • Obciążenie serwera: 55% mniej requestów związanych z tłumaczeniami
  • Zapytania do bazy: 75% mniej zapytań o tłumaczenia
  • Zużycie pamięci: 65% poprawy w działaniu aplikacji
  • Poziom błędów: 85% spadku problemów związanych z tłumaczeniami

Przewaga w skalowalności

Ta architektura rośnie razem z biznesem:

  • Nowe języki: dokładasz bez kary wydajnościowej
  • Nowe rynki: każdy dostaje zoptymalizowane ładowanie
  • Rozrost treści: tłumaczenia zostają podzielone na chunki i wciąż są wydajne
  • Rosnący zespół: developerzy bez problemu dodają tłumaczenia pod własne strony

Plan wdrożenia

Etap 1: fundamenty

  • Włączenie lazy loadingu w konfiguracji i18n
  • Implementacja composable'a useLocalesLoader
  • Zaprojektowanie strategii dzielenia tłumaczeń na chunki

Etap 2: optymalizacja

  • Uruchomienie monitoringu wydajności
  • Wdrożenie strategii fallbacku
  • Dodanie narzędzi dla developerów

Etap 3: skala

  • Pilnowanie, jak rośnie rozmiar bundle'a
  • Dopracowanie granic między chunkami
  • Sprawne wchodzenie na nowe rynki

Najważniejsze wnioski

  1. Dramatyczna poprawa jest możliwa: 92% mniejszy bundle dzięki sprytnemu ładowaniu
  2. Wydajność to satysfakcja użytkownika: 47% szybsze ładowanie napędza zaangażowanie
  3. Strategiczne dzielenie na chunki działa: ładuj to, czego potrzebujesz, wtedy gdy tego potrzebujesz
  4. Najwięcej zyskują użytkownicy zagraniczni: często mają wolniejsze łącza
  5. Developer experience ma znaczenie: dobre narzędzia sprawiają, że optymalizacja jest do utrzymania

Kiedy potrzebujesz tej optymalizacji

Sygnały ostrzegawcze, że twoja strona tego wymaga:

  • Wyniki Lighthouse poniżej 50
  • Początkowe bundle większe niż 500 KB
  • Wsparcie wielu języków, które spowalnia stronę
  • Wysoki bounce rate na rynkach zagranicznych
  • Pliki z tłumaczeniami ładowane bez potrzeby

Idealni kandydaci:

  • Wielojęzyczne platformy e-commerce
  • Międzynarodowe aplikacje SaaS
  • Serwisy contentowe obsługujące globalną widownię
  • Każda aplikacja z 3 lub więcej wariantami językowymi

Ta optymalizacja odmieniła naszą platformę: 92% mniejszy początkowy bundle, 48% szybsze ładowanie i komplet zielonych wyników w Lighthouse na 11 międzynarodowych rynkach.

Na czym polegał przełom? Na zrozumieniu, że nie każdy odwiedzający potrzebuje od razu wszystkich tłumaczeń. Sprytne ładowanie daje lepszą wydajność, zadowolonych użytkowników i architekturę, która się skaluje.


Realne wyniki optymalizacji tłumaczeń — obsługa klientów z 11 europejskich rynków przy o 92% mniejszym początkowym bundle'u.