Kurs Vue.js · Moduł 15: TypeScript i Nuxt

Nuxt 3 - podstawy

3 min czytania
W tej lekcji8

Nuxt 3 to meta-framework zbudowany na Vue 3, który daje SSR, SSG, routing plikowy i wiele więcej - od razu, bez konfiguracji. Można go porównać do systemu autopilota stacji NOVA LAB - wszystko jest skonfigurowane i gotowe do działania.

Tworzenie projektu Nuxt 3

1npx nuxi@latest init nova-lab-app
2cd nova-lab-app
3npm install
4npm run dev

Routing plikowy (File-Based Routing)

W Nuxt 3 nie konfigurujesz routera ręcznie. Struktura plików w folderze pages/ automatycznie tworzy trasy:

1pages/
2├── index.vue          →  /
3├── about.vue          →  /about
4├── crew/
5│   ├── index.vue      →  /crew
6│   └── [id].vue       →  /crew/123
7└── modules/
8    └── [...slug].vue  →  /modules/any/nested/path

Plik index.vue to strona główna danego katalogu. Pliki z nawiasami kwadratowymi [id].vue to parametry dynamiczne.

Layouts

Layouty definiują wspólną strukturę stron (nawigacja, stopka):

1<!-- layouts/default.vue -->
2<template>
3  <div class="station">
4    <nav class="nav">
5      <NuxtLink to="/">Home</NuxtLink>
6      <NuxtLink to="/about">About</NuxtLink>
7      <NuxtLink to="/crew">Crew</NuxtLink>
8    </nav>
9
10    <main>
11      <slot />
12    </main>
13
14    <footer>NOVA LAB &copy; 2087</footer>
15  </div>
16</template>

Strony automatycznie używają layoutu default. Można zmienić layout na konkretnej stronie:

1<!-- pages/dashboard.vue -->
2<script setup lang="ts">
3definePageMeta({
4  layout: 'admin'
5})
6</script>

useFetch - pobieranie danych

useFetch to composable Nuxta do pobierania danych. Działa zarówno na serwerze (SSR) jak i na kliencie:

1<script setup lang="ts">
2interface CrewMember {
3  id: number
4  name: string
5  role: string
6}
7
8// useFetch działa na serwerze i kliencie
9const { data: crew, pending, error } = await useFetch<CrewMember[]>(
10  '/api/crew'
11)
12</script>
13
14<template>
15  <div v-if="pending">Ładowanie...</div>
16  <div v-else-if="error">Błąd: {{ error.message }}</div>
17  <ul v-else>
18    <li v-for="member in crew" :key="member.id">
19      {{ member.name }} - {{ member.role }}
20    </li>
21  </ul>
22</template>

useFetch automatycznie:

  • Wykonuje request na serwerze podczas SSR
  • Serializuje dane i przesyła je do klienta (unika podwójnego fetchowania)
  • Udostępnia stany pending, error, data, refresh

useAsyncData - bardziej elastyczne

useAsyncData daje pełną kontrolę nad logiką pobierania danych:

1<script setup lang="ts">
2const { data: stats } = await useAsyncData('station-stats', () => {
3  return $fetch('/api/stats')
4})
5
6// Z transformacją danych
7const { data: modules } = await useAsyncData(
8  'modules',
9  () => $fetch('/api/modules'),
10  {
11    transform: (data) => data.filter(m => m.active),
12    watch: [selectedSector]  // automatyczne odświeżanie
13  }
14)
15</script>

Auto-imports

Nuxt 3 automatycznie importuje:

  • Composables Vue (ref, computed, watch...)
  • Composables Nuxt (useFetch, useRoute, useState...)
  • Komponenty z components/
  • Utility z utils/ i composables/

Nie trzeba pisać import { ref } from 'vue' - Nuxt robi to za nas!

1<script setup lang="ts">
2// Bez importów! Nuxt auto-importuje ref, computed, useFetch
3const count = ref(0)
4const doubled = computed(() => count.value * 2)
5
6const { data } = await useFetch('/api/data')
7</script>

Struktura projektu Nuxt 3

1nova-lab-app/
2├── pages/          # Routing plikowy
3├── components/     # Auto-importowane komponenty
4├── composables/    # Auto-importowane composables
5├── layouts/        # Layouty stron
6├── server/         # API endpoints (Nitro)
7│   └── api/        # /api/* routes
8├── public/         # Pliki statyczne
9├── app.vue         # Root komponent
10└── nuxt.config.ts  # Konfiguracja Nuxt

Server API (Nitro)

Nuxt 3 używa Nitro do obsługi endpointów API:

1// server/api/crew.ts
2export default defineEventHandler((event) => {
3  return [
4    { id: 1, name: 'Dr. Nova', role: 'Commander' },
5    { id: 2, name: 'Atlas', role: 'Navigator' },
6    { id: 3, name: 'Echo', role: 'Engineer' }
7  ]
8})
Kod do tej lekcji: App.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4// Symulacja struktury Nuxt 3
5// (Sandpack nie obsluguje Nuxt, wiec symulujemy koncepcje)
6
7type Page = 'home' | 'crew' | 'modules' | 'crew-detail'
8
9const currentPage = ref<Page>('home')
10const selectedCrewId = ref<number | null>(null)
11
12// Symulacja useFetch
13interface CrewMember {
14  id: number
15  name: string
16  role: string
17}
18
19interface StationModule {
20  id: string
21  name: string
22  status: string
23}
24
25// Dane "z API"
26const crewData: CrewMember[] = [
27  { id: 1, name: 'Dr. Nova', role: 'Commander' },
28  { id: 2, name: 'Atlas', role: 'Navigator' },
29  { id: 3, name: 'Echo', role: 'Engineer' },
30  { id: 4, name: 'Vega', role: 'Scientist' }
31]
32
33const modulesData: StationModule[] = [
34  { id: 'm1', name: 'Life Support', status: 'online' },
35  { id: 'm2', name: 'Navigation', status: 'online' },
36  { id: 'm3', name: 'Science Lab', status: 'maintenance' }
37]
38
39// Symulacja loading state
40const pending = ref(false)
41
42function navigate(page: Page, id?: number) {
43  pending.value = true
44  setTimeout(() => {
45    currentPage.value = page
46    if (id) selectedCrewId.value = id
47    pending.value = false
48  }, 300)
49}
50
51function getCrewMember(id: number) {
52  return crewData.find(c => c.id === id)
53}
54</script>
55
56<template>
57  <div class="nova-lab">
58    <nav class="nav">
59      <button @click="navigate('home')" :class="{ active: currentPage === 'home' }">Home</button>
60      <button @click="navigate('crew')" :class="{ active: currentPage === 'crew' }">Crew</button>
61      <button @click="navigate('modules')" :class="{ active: currentPage === 'modules' }">Modules</button>
62    </nav>
63
64    <div v-if="pending" class="loading">Ladowanie...</div>
65
66    <div v-else>
67      <!-- pages/index.vue -->
68      <div v-if="currentPage === 'home'" class="page">
69        <h1>NOVA LAB - Nuxt 3 Demo</h1>
70        <p>Symulacja file-based routing i useFetch</p>
71        <div class="info-box">
72          <p><strong>pages/</strong> index.vue -> /</p>
73          <p><strong>pages/</strong> crew/index.vue -> /crew</p>
74          <p><strong>pages/</strong> crew/[id].vue -> /crew/:id</p>
75          <p><strong>pages/</strong> modules.vue -> /modules</p>
76        </div>
77      </div>
78
79      <!-- pages/crew/index.vue -->
80      <div v-if="currentPage === 'crew'" class="page">
81        <h1>Zaloga</h1>
82        <p class="note">Dane pobrane przez useFetch('/api/crew')</p>
83        <div
84          v-for="member in crewData"
85          :key="member.id"
86          class="card clickable"
87          @click="navigate('crew-detail', member.id)"
88        >
89          <strong>{{ member.name }}</strong>
90          <span>{{ member.role }}</span>
91        </div>
92      </div>
93
94      <!-- pages/crew/[id].vue -->
95      <div v-if="currentPage === 'crew-detail' && selectedCrewId" class="page">
96        <button @click="navigate('crew')" class="back-btn">Wstecz</button>
97        <h1>{{ getCrewMember(selectedCrewId)?.name }}</h1>
98        <p>Rola: {{ getCrewMember(selectedCrewId)?.role }}</p>
99        <p class="note">Route: /crew/{{ selectedCrewId }}</p>
100      </div>
101
102      <!-- pages/modules.vue -->
103      <div v-if="currentPage === 'modules'" class="page">
104        <h1>Moduly stacji</h1>
105        <p class="note">useAsyncData('modules', () => $fetch('/api/modules'))</p>
106        <div v-for="mod in modulesData" :key="mod.id" class="card">
107          <strong>{{ mod.name }}</strong>
108          <span :class="mod.status">{{ mod.status }}</span>
109        </div>
110      </div>
111    </div>
112  </div>
113</template>
114
115<style scoped>
116.nova-lab {
117  background: #0a0e27;
118  color: #00ff88;
119  min-height: 100vh;
120  padding: 20px;
121  font-family: monospace;
122}
123h1 { color: #00ff88; margin-bottom: 10px; }
124.nav {
125  display: flex;
126  gap: 8px;
127  margin-bottom: 15px;
128  border-bottom: 1px solid rgba(0,180,216,0.3);
129  padding-bottom: 12px;
130}
131.nav button {
132  background: rgba(0,180,216,0.1);
133  color: #00b4d8;
134  border: 1px solid #00b4d8;
135  padding: 8px 18px;
136  border-radius: 6px;
137  cursor: pointer;
138  font-family: monospace;
139}
140.nav button.active { background: #00b4d8; color: white; }
141.page { padding: 10px 0; }
142.card {
143  display: flex;
144  justify-content: space-between;
145  background: rgba(0,180,216,0.08);
146  border: 1px solid #00b4d8;
147  padding: 12px 16px;
148  margin: 6px 0;
149  border-radius: 8px;
150}
151.card.clickable { cursor: pointer; }
152.card.clickable:hover { background: rgba(0,180,216,0.15); }
153.note { color: #666; font-size: 12px; margin-bottom: 10px; }
154.info-box {
155  background: rgba(0,255,136,0.05);
156  border: 1px solid rgba(0,255,136,0.2);
157  padding: 14px;
158  border-radius: 8px;
159  margin-top: 10px;
160  font-size: 13px;
161}
162.info-box p { margin: 4px 0; color: #ccc; }
163.loading { color: #f39c12; text-align: center; padding: 40px; }
164.back-btn {
165  background: none;
166  color: #00b4d8;
167  border: 1px solid #00b4d8;
168  padding: 6px 14px;
169  border-radius: 6px;
170  cursor: pointer;
171  margin-bottom: 10px;
172  font-family: monospace;
173}
174.online { color: #00ff88; }
175.maintenance { color: #f39c12; }
176.offline { color: #e74c3c; }
177</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jak Nuxt 3 tworzy routing aplikacji?

  2. 2. W którym folderze Nuxt 3 umieszcza się pliki, które automatycznie stają się trasami (routes) aplikacji?

To 2 z 6 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Użyj onMounted do dostępu do window - obiekt niedostępny na serwerze.

  • Układanie w poziomie

    Ułóż ścieżkę pliku dla dynamicznego route /crew/123 w Nuxt:

  • Układanie w pionie

    Uporządkuj kroki Incremental Static Regeneration:

  • Klikanie w kolejności

    Ułóż stany useFetch w kolejności typowego przepływu:

  • Układanie w poziomie

    Ułóż strukturę layoutu Nuxt z komponentem NuxtLink:

  • Układanie w pionie

    Uporządkuj strategie od najlepszego SEO do najsłabszego:

  • Klikanie w kolejności

    Ułóż polecenia do stworzenia i uruchomienia projektu Nuxt 3:

  • Układanie w pionie

    Uporządkuj koncepcje TypeScript w Vue od najprostszych do najbardziej zaawansowanych:

  • Układanie w pionie

    Uporządkuj cykl życia danych pobranych przez useFetch w Nuxt SSR:

  • Układanie w poziomie

    Ułóż nazwę głównego pliku konfiguracyjnego Nuxt 3:

  • Edytor kodu

    Zdefiniuj interfejsy StationModule i Alert, typuj props, emits i computed.

Przydatne artykuły