Nowy standard. Twoje centrum tworzenia inteligentnego, szybkiego i zrównoważonego oprogramowania AI

Zobacz teraz
Abstrakcyjna grafika przedstawiająca komponenty serwerowe Nuxt jako graf węzłów, częściowo zielonych, częściowo fioletowych.

Nuxt Server Components (Islands): praktyczny przewodnik po komponentach tylko po stronie serwera w Nuxt 4

Jakub Andrzejewski
|   Updated Oct 4, 2026

Od redakcji: Jakub Andrzejewski napisał ten przewodnik w marcu 2025 roku. W październiku 2026 zespół redakcyjny Monterail zaktualizował go do Nuxt 4.5. Poprawiliśmy przykłady kodu tak, by odpowiadały temu, jak dziś działają wyspy w Nuxt, i dodaliśmy sekcje o tym, jak działają od środka, o bezpieczeństwie, kompromisach oraz sytuacjach, w których lepiej z nich nie korzystać.

Komponenty serwerowe Nuxt to komponenty Vue renderowane wyłącznie po stronie serwera. Ich HTML trafia na stronę, ale ich JavaScript ani biblioteki, od których zależą, nigdy nie trafiają do przeglądarki.

Tworzysz je, dodając do pliku komponentu rozszerzenie .server.vue. Nuxt renderuje go wtedy jako „wyspę”, czyli statyczny fragment HTML wygenerowany po stronie serwera wewnątrz interaktywnej aplikacji Nuxt.

Podsumowanie

W większości aplikacji Vue użytkownik pobiera, parsuje i wykonuje JavaScript, którego jedynym zadaniem jest odtworzenie HTML, który już widzi na ekranie. Komponenty serwerowe Nuxt usuwają ten koszt, po jednym komponencie na raz.

Renderery Markdown, narzędzia do podświetlania składni, bloki z CMS-a i stopki pozostają na serwerze razem ze swoimi zależnościami, a przeglądarka dostaje zwykły HTML. Efekt to mniejsze bund­le, szybsza interaktywność i prostsze zarządzanie sekretami. Koszt to dodatkowa runda sieciowa przy nawigacji po stronie klienta, funkcja nadal oznaczona jako eksperymentalna oraz ścisły model izolacji, który twój zespół musi rozumieć.

Jeśli używasz ich wybiórczo przy ciężkich, nieinteraktywnych treściach, wyspy są jednym z najtańszych sposobów na poprawę wydajności w ekosystemie Vue. Jeśli używasz ich wszędzie, spowalniają aplikację.

Czym są komponenty serwerowe w Nuxt?

Komponent serwerowy Nuxt, nazywany też komponentem tylko po stronie serwera albo komponentem island, renderuje się na serwerze i wysyła do klienta wyłącznie HTML, bez JavaScriptu komponentu. Nuxt.js, rozbudowany framework Vue, od dawna jest w czołówce nowoczesnego tworzenia stron i aplikacji internetowych. Oferuje renderowanie po stronie serwera (SSR), generowanie stron statycznych (SSG) i renderowanie hybrydowe. Komponenty serwerowe dodają do tych trybów bardziej precyzyjną opcję: zamiast decydować, jak renderuje się cała strona, decydujesz o tym dla pojedynczego komponentu.

To rozróżnienie ma znaczenie ze względu na to, jak działa standardowe SSR. Sama dokumentacja Nuxt ujmuje to wprost. Domyślnie Nuxt renderuje aplikację na serwerze, ale potem wysyła do przeglądarki JavaScript dla każdego komponentu i hydratuje całą stronę. W przypadku treści, które po stronie klienta nigdy się nie zmieniają, „to zbędna praca: użytkownik pobiera, parsuje i wykonuje kod, którego jedynym zadaniem jest odtworzenie HTML, który już znajduje się na stronie”.

Komponenty serwerowe odwracają ten model. W przeciwieństwie do tradycyjnych komponentów Vue, które renderują się na serwerze, a potem są ponownie hydratuwane przy użyciu JavaScriptu po stronie klienta, komponenty serwerowe są przetwarzane w całości na serwerze. Nie trzeba wysyłać ich do przeglądarki jako JavaScriptu, co ogranicza wykonywanie kodu po stronie klienta. Ciężki parser Markdown albo narzędzie do podświetlania składni użyte wewnątrz komponentu serwerowego nigdy nie trafia do twojego bundla po stronie klienta.

Komponenty serwerowe pozwalają deweloperom Nuxtzachować interaktywny charakter aplikacji jednostronicowej, a jednocześnie płacić koszt JavaScriptu tylko za te części, które faktycznie są interaktywne. Jak zauważa Daniel Roe, lider zespołu core Nuxt, w swoim przewodniku po komponentach serwerowych Nuxt, nie są one magicznym rozwiązaniem na wszystko. To „przydatna opcja wtedy, gdy do renderowania komponentu po stronie klienta potrzeba nieproporcjonalnie dużo kodu”.

Dlaczego komponenty serwerowe Nuxt mają znaczenie dla twojego produktu?

Komponenty serwerowe są ważne, bo JavaScript to najdroższy zasób, jaki dostarczasz użytkownikowi. Każdy kilobajt, który zostaje na serwerze, to jeden mniej do pobrania, sparsowania i wykonania przez użytkowników korzystających ze średniej klasy telefonów. Wraz z tym, jak aplikacje internetowe w Nuxtstają się coraz bardziej złożone, wydajność przestaje być tylko miłym dodatkiem, a zaczyna wpływać na współczynnik konwersji, pozycje w wyszukiwarce i liczbę zgłoszeń do wsparcia.

Co to oznacza z biznesowego punktu widzenia:

  • Mniejsze bundle, szybsza interakcja. Kod komponentu i jego zależności zostają na serwerze. To bezpośrednio zmniejsza obciążenie głównego wątku, które opóźnia interaktywność na stronach bogatych w treść.

  • Sekrety zostają po stronie serwera. Komponent serwerowy może łączyć się z bazą danych albo prywatnym API przy użyciu poświadczeń, których przeglądarka nigdy nie zobaczy. Daniel Roe nazywa to jednym ze sposobów na „rozdzielenie odpowiedzialności”. Drugą popularną opcją jest trasa serwerowa Nitro.

  • Przyjazne SEO z założenia. Wyszukiwarki dostają kompletny HTML bez czekania na renderowanie po stronie klienta. Dotyczy to każdego SSR w Nuxt, ale wyspy pozwalają zachować tę korzyść, jednocześnie ograniczając koszt JavaScriptu dla każdej strony.

  • Bez potrzeby utrzymywania serwera przy stronach statycznych. Domyślnie Nuxt prerenderuje komponenty serwerowe podczas nuxt generate, więc działają także na hostingu statycznym, o ile ich propsy nie zmieniają się w czasie działania aplikacji (roe.dev).

Związek między strategią renderowania a przychodami nie jest teoretyczny. Gdy Monterail przebudował stronę Easyship w Nuxt po wersji AngularJS, którą zespół określił jako „zdecydowanie za wolną i koszmarną pod względem SEO”, czasy ładowania i ogólna wydajność poprawiły się o 37%, a ruch organiczny wzrósł o 14%. Ten projekt wykorzystywał SSR w Nuxt, a nie konkretnie wyspy. Wyspy to kolejny, bardziej precyzyjny krok w tym samym kierunku: renderuj po stronie serwera wszystko, co nie potrzebuje przeglądarki.

Jak działają komponenty serwerowe Nuxt od środka?

Każdy komponent serwerowy jest renderowany przez osobny endpoint „wyspy”, który uruchamia nową, odizolowaną aplikację Vue na serwerze, renderuje tylko ten komponent i zwraca jego HTML. Komponenty serwerowe wykorzystują pod spodem komponent <NuxtIsland>. Zrozumienie konsekwencji tego modelu pozwala zespołom uniknąć większości błędów opisanych dalej w tym przewodniku.

Co dzieje się przy pierwszym ładowaniu, a co przy nawigacji po stronie klienta?

Przy pierwszym ładowaniu strony renderowanej na serwerze wyspy renderują się inline, bez dodatkowego żądania. Przy nawigacji po stronie klienta przeglądarka pobiera z serwera każdą wyspę obecną na stronie docelowej. Jak podaje dokumentacja Nuxt, wiąże się to z realnymi kosztami:

  • Wyspy blokują nawigację na czas rundy sieciowej, chyba że przekażesz props lazy razem ze slotem #fallback.

  • Strona z wieloma wyspami wykonuje wiele żądań przy każdej nawigacji.

Wyspy sprawdzają się najlepiej na stronach, do których użytkownicy trafiają przez pełne ładowanie strony, na przykład na stronach contentowych i marketingowych, albo tam, gdzie na stronę przypada tylko kilka wysp. Gdy <NuxtLink> prowadzi do strony tylko po stronie serwera, Nuxt prefetchuje także odpowiedź wyspy, więc runda sieciowa odbywa się jeszcze przed kliknięciem użytkownika.

Dlaczego wyspa nie może współdzielić stanu z resztą strony?

Ponieważ każda wyspa renderuje się we własnej, odizolowanej aplikacji Vue, więc stan nie przekracza granicy wyspy. W praktyce, jak podaje dokumentacja Nuxt:

  • Nie możesz współdzielić stanu (provide/inject, Pinia, useState) między stroną a wyspą. Zamiast tego przekazuj dane przez propsy.

  • useRoute() wewnątrz wyspy odzwierciedla własne żądanie wyspy, a nie stronę, na której jest użytkownik. Informacje o trasie przekazuj jawnie.

  • Route middleware nie uruchamia się podczas renderowania wysp.

  • Twoje pluginy uruchamiają się ponownie dla każdej wyspy, chyba że plugin zapisany składnią obiektową ustawia env: { islands: false }.

Jak propsy są wysyłane do wyspy?

Propsy są serializowane do JSON i wysyłane jako parametry zapytania GET. Dzięki temu odpowiedzi wysp mogą być кешowane przez twój serwer lub CDN, ale oznacza to też, że:

  • Propsy muszą dać się zserializować do JSON.

  • Długość URL ogranicza rozmiar propsów, więc nie przekazuj dużych ilości danych.

  • Propsy mogą pojawić się w logach dostępu serwera, cache'u CDN i nagłówkach Referer.

  • Propsy to niezaufane dane wejściowe od użytkownika, o czym więcej w sekcji o kompromisach.

Zmiana propsów wyspy wywołuje nowe żądanie, które ponownie renderuje komponent na serwerze i podmienia jego HTML na miejscu.

Jak komponenty serwerowe wypadają na tle innych opcji renderowania w Nuxt?

Z poniższej tabeli skorzystaj, by dobrać właściwą opcję renderowania dla każdego komponentu, a nie dla całej aplikacji.

Opcja

Miejsce renderowania

Czy JavaScript komponentu trafia do przeglądarki?

Interaktywny?

Koszt przy nawigacji po stronie klienta

Najlepsze zastosowanie

Zwykły komponent

Serwer (SSR), potem hydratacja po stronie klienta

Tak

Tak

Brak, poza bundlem

Większość interfejsu

.client.vue / <ClientOnly>

Tylko przeglądarka

Tak

Tak

Brak, poza bundlem

API dostępne tylko w przeglądarce, widżety, których nie da się renderować po stronie serwera

.server.vue / <NuxtIsland>

Tylko serwer

Nie

Nie, z wyjątkiem potomków nuxt-client i slotów dostarczanych przez komponent nadrzędny

Jedno żądanie na wyspę

Ciężkie, statyczne treści

Lazy hydration (hydrate-on-visible, hydrate-never itd.)

Serwer, hydratacja później albo wcale

Tak, ładowany leniwie

Tak, po hydratacji

Brak, poza bundlem

Interaktywne komponenty poniżej pierwszego ekranu

Lazy hydration warto tu wspomnieć, bo rozwiązuje pokrewny problem. Jeśli komponent musi być interaktywny, tylko nie od razu, hydrate-on-visible albo hydrate-on-interaction będzie zwykle lepszym wyborem niż wyspa. Obie opcje są częścią szerszego zestawu technik poprawy wydajności w Vue, które warto znać.

Czym wyspy w Nuxt różnią się od React Server Components i Astro Islands?

Komponenty serwerowe Nuxt mają wspólną nazwę z React Server Components (RSC), ale działają inaczej, a względem wysp w stylu Astro są ich lustrzanym odbiciem. Daniel Roe opisuje RSC jako „zupełnie inne podejście do renderowania komponentów serwerowych, często powiązane ze strumieniowaniem odpowiedzi z serwera do klienta”. Wyspy w Nuxt są renderowane przez dedykowany endpoint i osadzane jako HTML.

Porównanie z Astro jest jeszcze bardziej użyteczne. „Architektura wysp”, spopularyzowana przez Astro i îles, osadza dynamiczne wyspy wewnątrz statycznej strony. Jak ujmuje to Roe: „Podejście Nuxt jest odwrotne: osadzamy statyczne ‘wyspy’ wewnątrz dynamicznej aplikacji Nuxt”.

Komponenty serwerowe Nuxt

React Server Components (Next.js)

Wyspy Astro

Domyślny tryb aplikacji

Interaktywna SPA z SSR

Drzewo komponentów z priorytetem serwera

Statyczny HTML

Czym jest „wyspa”

Statyczny komponent renderowany po stronie serwera

To nie jest model wysp

Interaktywny komponent

Sposób dostarczania

Inline przy pierwszym ładowaniu, endpoint wyspy przy nawigacji

Strumieniowany payload RSC

Hydratowany JavaScript dla każdej wyspy

Status

Eksperymentalne

Stabilne w Next.js App Router

Stabilne

Jeśli twój zespół rozważa Nuxt i Next.js, ta różnica ma większe znaczenie niż jakakolwiek lista funkcji. React traktuje komponenty serwerowe jako domyślne i wymaga świadomego włączenia kodu po stronie klienta. Nuxt domyślnie stawia na komponenty interaktywne i pozwala wyłączyć klienta dla pojedynczych komponentów.

Jak utworzyć i używać komponentu tylko po stronie serwera w Nuxt 4?

Dodaj do pliku komponentu rozszerzenie .server.vue i używaj go jak każdego innego komponentu. W aktualnych wersjach Nuxt nie wymaga to żadnej konfiguracji.

Krok 1: sprawdź konfigurację, choć prawdopodobnie nie musisz nic zmieniać

Starsze przewodniki, w tym pierwsza wersja tego tekstu, zalecały ustawienie experimental.componentIslands: true. To już nie jest potrzebne. Jak podaje dokumentacja Nuxt 4, domyślna wartość tej opcji to 'auto', co włącza funkcję od razu, gdy twoja aplikacja zawiera komponent serwerowy lub wyspę.

Jawna konfiguracja jest potrzebna tylko przy selektywnej hydratacji po stronie klienta albo zdalnych wyspach:

// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    componentIslands: {
      selectiveClient: true, // or 'deep', to enable `nuxt-client`
      remoteIsland: false, // allow rendering islands from a remote source
    },
  },
})

Komponenty serwerowe nadal są oznaczone jako eksperymentalne. Postępy możesz śledzić w issue z roadmapą komponentów serwerowych na GitHubie.

Krok 2: utwórz komponent

Poniżej znajdziesz komponent serwerowy, który pobiera wpis i go renderuje. Komponent nadrzędny przekazuje niewielki identyfikator, a nie cały wpis, a sam komponent pobiera swoje dane na serwerze:

<!-- app/components/BlogPost.server.vue -->
<script setup lang="ts">
const props = defineProps<{ slug: string }>()

const post = await $fetch<{ id: number; title: string; content: string }>(
  `https://api.example.com/posts/${props.slug}`,
)
</script>

<template>
  <article>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </article>
</template>

Możesz użyć go wszędzie, dokładnie tak jak zwykłego komponentu:

<!-- app/pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute()
</script>

<template>
  <div>
    <BlogPost :slug="String(route.params.slug)" />
  </div>
</template>

Zapamiętaj dwie zasady:

  • Komponent serwerowy musi mieć jeden element główny. Komentarze HTML liczą się jako elementy, więc komentarz na początku łamie tę zasadę.

  • Poczekaj na wywołania asynchroniczne. Bez awaitrenderowanie może się zakończyć, zanim dane dotrą.

Krok 3: dodaj interaktywność tam, gdzie jej potrzebujesz

Wyspa jest domyślnie statyczna, więc przycisk umieszczony w jej środku nie zareaguje na kliknięcie. Masz dwa wspierane sposoby, by dodać interaktywne elementy.

Opcja A: hydratuj pojedyncze komponenty potomne za pomocą nuxt-client. Gdy włączysz selectiveClient (zobacz krok 1), dodaj atrybut nuxt-client do komponentu wewnątrz wyspy. Nuxt najpierw renderuje go po stronie serwera jako część wyspy, a potem hydratuje w przeglądarce tylko ten komponent:

<!-- app/components/BlogPost.server.vue -->
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <!-- Only LikeButton's chunk is shipped to the client -->
    <LikeButton nuxt-client :post-id="post.id" />
  </article>
</template>

Używaj nuxt-client tylko we własnych lokalnych komponentach jednoplikiowych .vue. Wbudowane komponenty, takie jak <NuxtLink>, pomijają transformację wysp, więc najpierw opakuj je we własnym pliku .vue (nuxt/nuxt#29251).

Opcja B: przekaż interaktywną treść przez slot. Treść slotu pochodzi z komponentu nadrzędnego, więc należy do głównej aplikacji po stronie klienta i jest interaktywna:

<!-- app/components/BlogPost.server.vue -->
<template>
  <article>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
    <slot />
  </article>
</template>
<!-- app/pages/blog/[slug].vue -->
<script setup lang="ts">
const slug = String(useRoute().params.slug)
</script>

<template>
  <BlogPost :slug="slug">
    <LikeButton :post-id="slug" />
  </BlogPost>
</template>

<ClientOnly> nie zastępuje żadnej z tych opcji. Wewnątrz wyspy nic się nie hydratuje, jeśli nie zostało oznaczone jako nuxt-client albo przekazane przez komponent nadrzędny przez slot.

Krok 4: unikaj blokowania nawigacji za pomocą

Przy nawigacji po stronie klienta każda wyspa oznacza żądanie sieciowe. Przekaż props lazy i slot #fallback, aby strona mogła się renderować, gdy wyspa się ładuje. Fallback wyświetli się też wtedy, gdy pobranie wyspy się nie powiedzie:

<template>
  <BlogPost :slug="slug" lazy>
    <template #fallback>
      <p>Loading article…</p>
    </template>
  </BlogPost>
</template>

Krok 5 (opcjonalnie): renderuj islands bezpośrednio za pomocą

Komponenty w katalogu ~/components/islands/ są rejestrowane jako islands i możesz renderować je bezpośrednio przez <NuxtIsland>. Dzięki temu masz dostęp do zdarzenia error i metody refresh(), których nie udostępnia automatycznie generowany wrapper .server.vue (nuxt/nuxt#25744):

<template>
  <NuxtIsland name="PostStats" :props="{ slug }" lazy>
    <template #fallback>
      <p>Loading stats…</p>
    </template>
  </NuxtIsland>
</template>

Możesz też połączyć Comments.server.vue z Comments.client.vue. Nuxt najpierw renderuje część serwerową, a po zamontowaniu podmienia ją na część kliencką. W takim przypadku komponent nie jest island, a część kliencka hydratuje się normalnie.

Kiedy warto używać komponentów serwerowych Nuxt? Praktyczne zastosowania

Używaj komponentów serwerowych do treści, których renderowanie po stronie klienta jest kosztowne, które nie zmieniają się po załadowaniu i pojawiają się na stronie tylko w ograniczonej liczbie. Poniższe przykłady pokazują, gdzie sprawdzają się najlepiej i na co trzeba uważać.

  1. Serwisy z dużą ilością treści, takie jak blogi, portale newsowe i dokumentacja. Serwis z dokumentacją, taki jak oficjalna dokumentacja Vue, może używać komponentów serwerowych do renderowania plików markdown lub treści z headless CMS po stronie serwera, dzięki czemu parser i narzędzie do podświetlania składni nie trafią do bundla klienta. Dokumentacja Nuxt pokazuje dokładnie taki przykład: komponent HighlightedMarkdown.server.vue, w którym „biblioteki do parsowania i podświetlania markdownu nie są dołączane do bundla klienta”. To kanoniczny przypadek użycia. Jeśli połączysz to z prerenderingiem, identyczne islands zostaną wyrenderowane raz i użyte ponownie.

  2. Statyczne elementy obecne w całym serwisie, takie jak stopki, mega-menu czy bloki prawne. Daniel Roe przerobił stopkę swojej strony, po prostu dodając .server.vue, bo „nic tutaj nie musi być dynamiczne”. Od wersji Nuxt 4.5.2 linki renderowane wewnątrz komponentów serwerowych domyślnie obsługują nawigację po stronie klienta, co eliminuje potrzebę stosowania popularnego obejścia (roe.dev).

  3. Spersonalizowane treści i sekrety dostępne wyłącznie po stronie serwera. Panel użytkownika ze spersonalizowanymi rekomendacjami może pobierać dane przypisane do konkretnego użytkownika bez ujawniania kluczy API w warstwie front-endowej. Trzeba jednak uważać: odpowiedzi islands mogą być cache'owane i są kluczowane według nazwy, propsów i kontekstu. Upewnij się, że spersonalizowane islands nigdy nie są cache'owane na poziomie CDN, i pamiętaj, że middleware tras nie uruchamia się dla islands. Autoryzację egzekwuj bezpośrednio w warstwie danych.

  4. Agregacja API po stronie serwera. Platformy do rezerwacji podróży agregują dane z wielu usług, takich jak loty i hotele, po stronie serwera, zanim wyślą jedną odpowiedź, co ogranicza liczbę wywołań API po stronie klienta. Island może wyrenderować zagregowany wynik jako HTML. Jeśli klient potrzebuje surowych danych, na przykład do filtrowania albo sortowania, lepszym rozwiązaniem będzie trasa serwerowa Nitro działająca w twoim Node.js backend.

  5. Wyniki działania AI i uczenia maszynowego. Rekomendacje oparte na AI albo wygenerowany tekst mogą powstawać po stronie serwera, a do klienta trafia wtedy tylko końcowy rezultat, co chroni własne prompty i logikę modelu. Trzeba jednak pamiętać, że powolne wnioskowanie wewnątrz island blokuje nawigację. Użyj lazy z #fallback albo streamuj wynik z trasy serwerowej, jeśli odpowiedź zajmuje więcej niż chwilę.

  6. Landing page'e istotne z perspektywy SEO. W platformie społecznościowej dla fanów NFT Monterail wybrał Nuxt właśnie po to, by zwiększyć widoczność aplikacji w wynikach wyszukiwania Google. Islands rozwijają tę samą logikę. Strony marketingowe otwierane przy pełnym ładowaniu strony dostają kompletny HTML przy znacznie mniejszej ilości JavaScriptu, a właśnie w takim scenariuszu islands nie powodują spadku wydajności nawigacji.

Jakie błędy najczęściej popełnia się przy komponentach serwerowych Nuxt?

Prawie każdy błąd wynika z pominięcia jednego faktu: island to osobna, statyczna aplikacja Vue renderowana po stronie serwera. Komponenty serwerowe Nuxt dają dużo korzyści, ale nie nadużywaj ich i uważaj na te pułapki.

Błąd 1: Oczekiwanie, że island będzie współdzielić stan ze stroną

useState, useFetch i useAsyncData faktycznie działają wewnątrz komponentu serwerowego, po stronie serwera. Problem polega na tym, że island jest odizolowaną aplikacją, więc jego stan nigdy nie jest współdzielony ze stroną, która go otacza:

<!-- app/components/CartSummary.server.vue -->
<script setup lang="ts">
// ❌ This is a separate 'cart' from the one the page uses
const cart = useState('cart', () => [])
</script>

Jak to naprawić: przekaż przez propsy dane, których island potrzebuje. Jeśli komponent ma odczytywać i aktualizować współdzielony stan po stronie klienta, nie powinien być island. Zamiast tego zrób z niego zwykły komponent albo komponent potomny nuxt-client, który korzysta z twojego Pinia store.

Błąd 2: Przekazywanie dużych zbiorów danych przez propsy

Pobieranie danych na stronie i przekazywanie wyników dalej to dobry wzorzec w przypadku zwykłych komponentów. W islands działa to jednak przeciwko tobie, bo propsy są przesyłane jako parametry zapytania GET:

<!-- app/pages/blog/index.vue -->
<script setup lang="ts">
const { data: posts } = await useAsyncData('posts', () => $fetch('/api/posts'))
</script>

<template>
  <!-- ❌ If PostList is a .server.vue component, the whole array is serialized into a URL -->
  <PostList :posts="posts" />
</template>

Jak to naprawić: przekaż niewielki identyfikator i pozwól, by island sam pobrał swoje dane po stronie serwera:

<!-- app/components/PostList.server.vue -->
<script setup lang="ts">
const props = defineProps<{ category: string }>()

const posts = await $fetch<{ id: number; title: string }[]>('/api/posts', {
  query: { category: props.category },
})
</script>

<template>
  <ul>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

Błąd 3: Umieszczanie logiki interaktywnej wewnątrz island

Obsługa zdarzeń i lokalny stan w komponencie serwerowym nigdy nie uruchomią się w przeglądarce, bo żaden z jego JavaScriptowych zasobów nie jest wysyłany do klienta:

<!-- app/components/Counter.server.vue -->
<script setup lang="ts">
let count = 0
const increment = () => count++ // ❌ Never runs in the browser
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

Jak to naprawić: przenieś część interaktywną do zwykłego komponentu i renderuj ją przez nuxt-client albo przez slot (zobacz krok 3).

Błąd 4: Odczytywanie trasy wewnątrz island

<script setup lang="ts">
const route = useRoute() // ❌ Reflects the island's own request, not the user's page
</script>

Jak to naprawić: odczytaj trasę na stronie i przekaż przez propsy to, czego island potrzebuje, albo użyj propu context w <NuxtIsland>.

Błąd 5: Zapominanie o await przy wywołaniach asynchronicznych

<script setup lang="ts">
const posts = $fetch('/api/posts') // ❌ Missing await
</script>

Jak to naprawić: zawsze używaj await przy wywołaniach asynchronicznych, żeby dane były dostępne przed renderowaniem:

<script setup lang="ts">
const posts = await $fetch('/api/posts')
</script>

Błąd 6: Ufanie propsom island

Propsy island pochodzą z adresu URL żądania, więc każdy może je spreparować. Nigdy nie przekazuj niezwreryfikowanych propsów do <component :is>, h() ani resolveDynamicComponent(). Zamiast tego przepuść je przez listę dozwolonych wartości, zgodnie z zaleceniem dokumentacji Nuxt:

<script setup lang="ts">
import type { Component } from 'vue'
import CardA from './CardA.vue'
import CardB from './CardB.vue'

const props = defineProps<{ variant: string }>()
const allowed: Record<string, Component> = { a: CardA, b: CardB }
const component = allowed[props.variant] ?? CardA
</script>

<template>
  <component :is="component" />
</template>

Błąd 7: Zamienianie każdego komponentu w komponent serwerowy

Nie zamieniaj każdego komponentu w komponent serwerowy. Każdy island dodaje osobne żądanie sieciowe przy nawigacji po stronie klienta, islands mogą znacząco zwiększyć liczbę chunków generowanych podczas budowania aplikacji (nuxt/nuxt#34855), a każdy zagnieżdżony island dokłada kolejny narzut. Komponentów serwerowych używaj do ciężkich, statycznych i nieinteraktywnych treści, a do wszystkiego innego wybieraj zwykłe komponenty albo komponenty z leniwą hydratacją.

Jakie są kompromisy i ograniczenia komponentów serwerowych Nuxt?

Najważniejsze kompromisy to eksperymentalny status, dodatkowa podróż sieciowa dla każdego island przy nawigacji po stronie klienta, model izolacji uniemożliwiający współdzielenie stanu oraz propsy, które trzeba traktować jako niezaufane dane wejściowe. Nie ma tu drogi na skróty. Oto, na co godzi się twój zespół.

Czy to rozwiązanie jest gotowe do użycia produkcyjnego?

Komponenty serwerowe w Nuxt 4.5 nadal mają oficjalnie status eksperymentalny, ale są dostępne już od Nuxt 3 i działają w środowiskach produkcyjnych, także na stronie lidera zespołu Nuxt core. W praktyce ryzyko nie polega na tym, że nagle znikną. Chodzi raczej o niedopracowane elementy i zmiany zachowania między kolejnymi mniejszymi wydaniami. Przypnij wersję Nuxt, czytaj release notes i obejmij islands testami end-to-end.

Jakie kwestie bezpieczeństwa trzeba znać?

Dbaj o aktualność Nuxt. Nuxt v4.5.1, wydany w lipcu 2026 roku, był krytyczną aktualizacją bezpieczeństwa i oprócz innych poprawek usuwał m.in. „server-side RCE and unauthorized component instantiation via server island props” oraz „server component DoS” (release notes). Zespół Nuxt zalecił natychmiastową aktualizację przy użyciu npx nuxt upgrade --dedupe oraz wyczyszczenie cache CDN, jeśli korzystasz z reguł tras cache, swr lub isr. Propsy island traktuj tak, jak potraktowałaby je ocena bezpieczeństwa aplikacji webowej: jako niezaufane dane wejściowe.

Znaczenie ma też ścieżka aktualizacji. Nuxt 3 osiągnął koniec wsparcia 31 lipca 2026 roku i nie dostaje już poprawek bezpieczeństwa, a Nuxt 5 jest szacowany na czwarty kwartał 2026 roku (Nuxt roadmap). Jeśli nadal korzystasz z Nuxt 3, przejdź na Nuxt 4, zanim mocno zainwestujesz w islands.

Jakie są znane ograniczenia techniczne?

Dokumentacja Nuxt wymienia obecnie następujące niedopracowane elementy:

  • Większość funkcji islands, takich jak sloty i nuxt-client, działa tylko ze składnikami single-file components.

  • Template refs nie mogą z poziomu komponentu nadrzędnego wskazywać elementów wewnątrz komponentu serwerowego (#31512).

  • inject/provide nie działa między wyspami (#22751).

  • useId może generować kolidujące identyfikatory wewnątrz wysp i nie działa w interaktywnych komponentach osadzonych w wyspach (dokumentacja NuxtIsland).

  • W webpacku i Rspacku style scoped :slotted() w slotach komponentów serwerowych mogą nie działać poprawnie (#31510).

  • Na trasach renderowanych z użyciem noScripts komponenty nuxt-client nie przejdą hydratacji. Zwykłych statycznych wysp ten problem nie dotyczy.

Czy ten komponent powinien być komponentem serwerowym? Krótka lista kontrolna

Pytanie

Jeśli tak

Jeśli nie

Czy potrzebuje obsługi zdarzeń, stanu lokalnego albo API przeglądarki?

Zwykły komponent albo komponent kliencki (lub potomny nuxt-client)

Island candidate

Czy korzysta z ciężkiej biblioteki, na przykład do markdownu, podświetlania składni albo formatowania dat i i18n?

Strong island candidate

Korzyść będzie mniejsza

Czy potrzebuje danych z Pinia, useState albo provide/inject?

Przekaż to jako niewielkie propsy albo nie używaj wyspy

Island candidate

Czy dane wejściowe przekroczą rozmiar, który wygodnie mieści się w URL-u?

Przekaż identyfikator i pobierz dane wewnątrz wyspy

Fine

Czy pojawia się wiele razy na stronie albo na stronach, do których użytkownicy trafiają głównie przez nawigację po stronie klienta?

Używaj ostrożnie i rozważ lazy

Fine

Czy musi często aktualizować się po stronie klienta?

To nie jest wyspa

Fine

Czy komponenty serwerowe Nuxt będą warte uwagi dla twojego zespołu w 2026 roku?

Tak, w przypadku odpowiednich komponentów. Jeśli twoja aplikacja Vue wysyła ciężkie biblioteki renderujące dla treści, które nigdy się nie zmieniają, wyspy należą do najbardziej opłacalnych usprawnień wydajności. Dają mniej korzyści, jeśli twój interfejs jest głównie interaktywny albo większość nawigacji odbywa się po stronie klienta między wieloma małymi komponentami.

Najlepsze efekty daje traktowanie renderowania jako decyzji podejmowanej osobno dla każdego komponentu. Interaktywny interfejs zostaw jako zwykłe komponenty, interaktywność poniżej linii załamania odłóż dzięki lazy hydration, a ciężkie, statyczne treści przenieś do komponentów serwerowych. Taka hybrydowa architektura to kolejny powód, dla którego zespoły wybierają Nuxt zamiast modeli renderowania typu wszystko albo nic.

Żeby do tego dojść, potrzebujesz właściwej oceny sytuacji: musisz wiedzieć, które komponenty warto przekonwertować, jak mierzyć wpływ zmian i jak unikać problemów związanych z izolacją oraz bezpieczeństwem. To właśnie tym na co dzień zajmują się deweloperzy Vue.js i Nuxt w Monterail, pracując nad dedykowanymi aplikacjami internetowymi. Jak pokazuje rozmowa Monterail z Danielem Roe o pracy nad Nuxt, framework najlepiej wykorzystują zespoły, które rozumieją, jak działa pod maską.

FAQ

Jakub Andrzejewski
Jakub Andrzejewski
Former Senior Frontend Developer
Linkedin
Jakub Andrzejewski is a skilled Vue and Node.js developer with a strong focus on Web Performance optimization. As an active contributor to the Vue and Nuxt communities, Jakub creates Open-Source Software, shares insights through technical articles, and delivers engaging talks at technology conferences around the globe.