Frigjør kraften i Next.js App Router med vår dybdegående guide til filbasert ruting. Lær hvordan du strukturerer applikasjonen din, lager dynamiske ruter, håndterer layouter og mer.
Next.js App Router: En Omfattende Guide til Filbasert Ruting
Next.js App Router, introdusert i Next.js 13 og standard i senere versjoner, revolusjonerer hvordan vi strukturerer og navigerer i applikasjoner. Den introduserer et kraftig og intuitivt filbasert rutingsystem som forenkler utvikling, forbedrer ytelse og hever den generelle utvikleropplevelsen. Denne omfattende guiden vil dykke dypt inn i App Routerens filbaserte ruting, og gi deg kunnskapen og ferdighetene til å bygge robuste og skalerbare Next.js-applikasjoner.
Hva er Filbasert Ruting?
Filbasert ruting er et rutingsystem der strukturen på applikasjonens ruter bestemmes direkte av organiseringen av filene og mappene dine. I Next.js App Router definerer du ruter ved å opprette filer i `app`-mappen. Hver mappe representerer et rutesegment, og spesielle filer i disse mappene definerer hvordan det rutesegmentet skal håndteres. Denne tilnærmingen gir flere fordeler:
- Intuitiv Struktur: Filsystemet speiler applikasjonens rutestruktur, noe som gjør det enkelt å forstå og navigere.
- Automatisk Ruting: Next.js genererer automatisk ruter basert på filstrukturen din, noe som fjerner behovet for manuell konfigurasjon.
- Samlokalisering av Kode: Rutehåndterere og UI-komponenter er plassert sammen, noe som forbedrer kodeorganisering og vedlikeholdbarhet.
- Innebygde Funksjoner: App Router gir innebygd støtte for layouter, dynamiske ruter, datahenting og mer, noe som forenkler komplekse rutingscenarioer.
Komme i Gang med App Router
For å bruke App Router, må du opprette et nytt Next.js-prosjekt eller migrere et eksisterende prosjekt. Sørg for at du bruker Next.js versjon 13 eller nyere.
Opprette et Nytt Prosjekt:
Du kan opprette et nytt Next.js-prosjekt med App Router ved å bruke følgende kommando:
npx create-next-app@latest my-app --example with-app
Migrere et Eksisterende Prosjekt:
For å migrere et eksisterende prosjekt, må du flytte sidene dine fra `pages`-mappen til `app`-mappen. Du må kanskje justere rutingslogikken din tilsvarende. Next.js tilbyr en migreringsguide for å hjelpe deg med denne prosessen.
Kjernekonsepter for Filbasert Ruting
App Router introduserer flere spesielle filer og konvensjoner som definerer hvordan rutene dine håndteres:
1. `app`-mappen
`app`-mappen er roten for applikasjonens ruter. Alle filer og mapper i denne mappen vil bli brukt til å generere ruter. Alt utenfor `app`-mappen (som `pages`-mappen hvis du migrerer) vil bli ignorert av App Router.
2. `page.js`-filen
`page.js` (eller `page.jsx`, `page.ts`, `page.tsx`)-filen er den mest grunnleggende delen av App Router. Den definerer UI-komponenten som skal rendres for et spesifikt rutesegment. Det er en **påkrevd** fil for ethvert rutesegment du vil ha direkte tilgjengelig.
Eksempel:
Hvis du har en filstruktur som dette:
app/
about/
page.js
Komponenten som eksporteres fra `app/about/page.js` vil bli rendret når en bruker navigerer til `/about`.
// app/about/page.js
import React from 'react';
export default function AboutPage() {
return (
<div>
<h1>Om Oss</h1>
<p>Lær mer om vårt selskap.</p>
</div>
);
}
3. `layout.js`-filen
`layout.js` (eller `layout.jsx`, `layout.ts`, `layout.tsx`)-filen definerer et brukergrensesnitt som deles på tvers av flere sider innenfor et rutesegment. Layouter er nyttige for å lage konsistente headere, footere, sidebarer og andre elementer som skal være til stede på flere sider.
Eksempel:
La oss si at du vil legge til en header på både `/about`-siden og en hypotetisk `/about/team`-side. Du kan opprette en `layout.js`-fil i `app/about`-mappen:
// app/about/layout.js
import React from 'react';
export default function AboutLayout({ children }) {
return (
<div>
<header>
<h1>Om Vårt Selskap</h1>
</header>
<main>{children}</main>
</div>
);
}
`children`-propen vil bli erstattet med UI-et som rendres av `page.js`-filen i samme mappe eller i eventuelle nestede mapper.
4. `template.js`-filen
`template.js`-filen ligner på `layout.js`, men den oppretter en ny instans av komponenten for hver barn-rute. Dette er nyttig for scenarier der du vil opprettholde komponenttilstand eller forhindre re-rendringer når du navigerer mellom barn-ruter. I motsetning til layouter, vil maler re-rendre ved navigering. Bruk av maler er flott for å animere elementer ved navigering.
Eksempel:
// app/template.js
'use client'
import { useState } from 'react'
export default function Template({ children }) {
const [count, setCount] = useState(0)
return (
<main>
<p>Mal: {count}</p>
<button onClick={() => setCount(count + 1)}>Oppdater mal</button>
{children}
</main>
)
}
5. `loading.js`-filen
`loading.js` (eller `loading.jsx`, `loading.ts`, `loading.tsx`)-filen lar deg lage et laste-UI som vises mens et rutesegment lastes. Dette er nyttig for å gi en bedre brukeropplevelse når du henter data eller utfører andre asynkrone operasjoner.
Eksempel:
// app/about/loading.js
import React from 'react';
export default function Loading() {
return <p>Laster informasjon om oss...</p>;
}
Når en bruker navigerer til `/about`, vil `Loading`-komponenten vises til `page.js`-komponenten er fullstendig rendret.
6. `error.js`-filen
`error.js` (eller `error.jsx`, `error.ts`, `error.tsx`)-filen lar deg lage et tilpasset feil-UI som vises når en feil oppstår i et rutesegment. Dette er nyttig for å gi en mer brukervennlig feilmelding og forhindre at hele applikasjonen krasjer.
Eksempel:
// app/about/error.js
'use client'
import React from 'react';
export default function Error({ error, reset }) {
return (
<div>
<h2>En feil har oppstått!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Prøv igjen</button>
</div>
);
}
Hvis en feil oppstår under renderingen av `/about`-siden, vil `Error`-komponenten vises. `error`-propen inneholder informasjon om feilen, og `reset`-funksjonen lar brukeren forsøke å laste siden på nytt.
7. Rutegrupper
Rutegrupper `(gruppenavn)` lar deg organisere rutene dine uten å påvirke URL-strukturen. De opprettes ved å pakke et mappenavn i parentes. Dette er spesielt nyttig for å organisere layouter og delte komponenter.
Eksempel:
app/
(marketing)/
about/
page.js
contact/
page.js
(shop)/
products/
page.js
I dette eksempelet er `about`- og `contact`-sidene gruppert under `marketing`-gruppen, og `products`-siden er under `shop`-gruppen. URL-ene forblir henholdsvis `/about`, `/contact` og `/products`.
8. Dynamiske Ruter
Dynamiske ruter lar deg lage ruter med variable segmenter. Dette er nyttig for å vise innhold basert på data hentet fra en database eller API. Dynamiske rutesegmenter defineres ved å pakke segmentnavnet i hakeparenteser (f.eks. `[id]`)
Eksempel:
La oss si at du vil lage en rute for å vise individuelle blogginnlegg basert på deres ID. Du kan lage en filstruktur som dette:
app/
blog/
[id]/
page.js
`[id]`-segmentet er et dynamisk segment. Komponenten som eksporteres fra `app/blog/[id]/page.js` vil bli rendret når en bruker navigerer til en URL som `/blog/123` eller `/blog/456`. Verdien av `id`-parameteren vil være tilgjengelig i `params`-propen til komponenten.
// app/blog/[id]/page.js
import React from 'react';
export default async function BlogPost({ params }) {
const { id } = params;
// Hent data for blogginnlegget med den gitte ID-en
const post = await fetchBlogPost(id);
if (!post) {
return <p>Blogginnlegg ikke funnet.</p>;
}
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
async function fetchBlogPost(id) {
// Simulerer henting av data fra en database eller API
return new Promise((resolve) => {
setTimeout(() => {
const posts = {
'123': { title: 'Mitt Første Blogginnlegg', content: 'Dette er innholdet i mitt første blogginnlegg.' },
'456': { title: 'Et Annet Blogginnlegg', content: 'Dette er noe mer spennende innhold.' },
};
resolve(posts[id] || null);
}, 500);
});
}
Du kan også bruke flere dynamiske segmenter i en rute. For eksempel kan du ha en rute som `/blog/[category]/[id]`.
9. Catch-all-segmenter
Catch-all-segmenter lar deg lage ruter som matcher et hvilket som helst antall segmenter. Dette er nyttig for scenarioer som å lage et CMS der URL-strukturen bestemmes av brukeren. Catch-all-segmenter defineres ved å legge til tre prikker før segmentnavnet (f.eks. `[...slug]`)
Eksempel:
app/
docs/
[...slug]/
page.js
`[...slug]`-segmentet vil matche et hvilket som helst antall segmenter etter `/docs`. For eksempel vil det matche `/docs/getting-started`, `/docs/api/users` og `/docs/advanced/configuration`. Verdien av `slug`-parameteren vil være en matrise som inneholder de matchede segmentene.
// app/docs/[...slug]/page.js
import React from 'react';
export default function DocsPage({ params }) {
const { slug } = params;
return (
<div>
<h1>Dokumentasjon</h1>
<p>Slug: {slug ? slug.join('/') : 'Ingen slug'}</p>
</div>
);
}
Valgfrie catch-all-segmenter kan opprettes ved å legge til segmentnavnet i doble hakeparenteser `[[...slug]]`. Dette gjør rutesegmentet valgfritt. Eksempel:
app/
blog/
[[...slug]]/
page.js
Denne oppsettet vil rendre page.js-komponenten både på `/blog` og `/blog/any/number/of/segments`.
10. Parallelle Ruter
Parallelle ruter lar deg samtidig rendre en eller flere sider i samme layout. Dette er spesielt nyttig for komplekse layouter, som dashboards, der forskjellige deler av siden kan lastes uavhengig. Parallelle ruter defineres ved hjelp av `@`-symbolet etterfulgt av et slot-navn (f.eks. `@sidebar`, `@main`).
Eksempel:
app/
@sidebar/
page.js // Innhold for sidebaren
@main/
page.js // Innhold for hovedseksjonen
default.js // Påkrevd: Definerer standardlayouten for parallelle ruter
`default.js`-filen er påkrevd når du bruker parallelle ruter. Den definerer hvordan de forskjellige slot-ene kombineres for å lage den endelige layouten.
// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
return (
<div style={{ display: 'flex' }}>
<aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
{sidebar}
</aside>
<main style={{ flex: 1, padding: '20px' }}>
{main}
</main>
</div>
);
}
11. Avskjærende Ruter
Avskjærende ruter (Intercepting Routes) lar deg laste en rute fra en annen del av applikasjonen din innenfor den nåværende layouten. Dette er nyttig for å lage modaler, bildegallerier og andre UI-elementer som skal vises oppå eksisterende sideinnhold. Avskjærende ruter defineres ved hjelp av `(..)`-syntaksen, som indikerer hvor mange nivåer opp i mappestrukturen man skal gå for å finne den avskårne ruten.
Eksempel:
app/
(.)photos/
[id]/
page.js // Den avskårne ruten
feed/
page.js // Siden der fotomodalen vises
I dette eksempelet, når en bruker klikker på et bilde på `/feed`-siden, blir `app/(.)photos/[id]/page.js`-ruten avskåret og vist som en modal oppå `/feed`-siden. `(.)`-syntaksen forteller Next.js å se ett nivå opp (til `app`-mappen) for å finne `photos/[id]`-ruten.
Datahenting med App Router
App Router gir innebygd støtte for datahenting ved hjelp av Server Components og Client Components. Server Components rendres på serveren, mens Client Components rendres på klienten. Dette lar deg velge den beste tilnærmingen for hver komponent basert på dens krav.
Serverkomponenter
Serverkomponenter er standarden i App Router. De lar deg hente data direkte i komponentene dine uten behov for separate API-ruter. Dette kan forbedre ytelsen og forenkle koden din.
Eksempel:
// app/products/page.js
import React from 'react';
export default async function ProductsPage() {
const products = await fetchProducts();
return (
<div>
<h1>Produkter</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
async function fetchProducts() {
// Simulerer henting av data fra en database eller API
return new Promise((resolve) => {
setTimeout(() => {
const products = [
{ id: 1, name: 'Produkt A' },
{ id: 2, name: 'Produkt B' },
{ id: 3, name: 'Produkt C' },
];
resolve(products);
}, 500);
});
}
I dette eksempelet kalles `fetchProducts`-funksjonen direkte i `ProductsPage`-komponenten. Komponenten rendres på serveren, og dataene hentes før HTML-en sendes til klienten.
Klientkomponenter
Klientkomponenter rendres på klienten og lar deg bruke klient-side funksjoner som hendelseslyttere, tilstand og nettleser-API-er. For å bruke en klientkomponent må du legge til `'use client'`-direktivet øverst i filen.
Eksempel:
// app/counter/page.js
'use client'
import React, { useState } from 'react';
export default function CounterPage() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Teller</h1>
<p>Antall: {count}</p>
<button onClick={() => setCount(count + 1)}>Øk</button>
</div>
);
}
I dette eksempelet er `CounterPage`-komponenten en klientkomponent fordi den bruker `useState`-hooken. `'use client'`-direktivet forteller Next.js å rendre denne komponenten på klienten.
Avanserte Rutingsteknikker
App Router tilbyr flere avanserte rutingsteknikker som kan brukes til å lage komplekse og sofistikerte applikasjoner.
1. Rutehåndterere (Route Handlers)
Rutehåndterere lar deg opprette API-endepunkter i `app`-mappen din. Dette fjerner behovet for en separat `pages/api`-mappe. Rutehåndterere defineres i filer som heter `route.js` (eller `route.ts`) og eksporterer funksjoner som håndterer forskjellige HTTP-metoder (f.eks. `GET`, `POST`, `PUT`, `DELETE`).
Eksempel:
// app/api/users/route.js
import { NextResponse } from 'next/server'
export async function GET(request) {
// Simulerer henting av brukere fra en database
const users = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
];
return NextResponse.json(users);
}
export async function POST(request) {
const body = await request.json()
console.log('Mottatt data:', body)
return NextResponse.json({ message: 'Bruker opprettet' }, { status: 201 })
}
Dette eksempelet definerer en rutehåndterer på `/api/users` som håndterer både `GET`- og `POST`-forespørsler. `GET`-funksjonen returnerer en liste over brukere, og `POST`-funksjonen oppretter en ny bruker.
2. Rutegrupper med Flere Layouter
Du kan kombinere rutegrupper med layouter for å lage forskjellige layouter for forskjellige deler av applikasjonen din. Dette er nyttig for scenarioer der du vil ha en annen header eller sidebar for forskjellige deler av nettstedet ditt.
Eksempel:
app/
(marketing)/
layout.js // Markedsføringslayout
about/
page.js
contact/
page.js
(admin)/
layout.js // Admin-layout
dashboard/
page.js
I dette eksempelet vil `about`- og `contact`-sidene bruke `marketing`-layouten, mens `dashboard`-siden vil bruke `admin`-layouten.
3. Middleware
Middleware lar deg kjøre kode før en forespørsel håndteres av applikasjonen din. Dette er nyttig for oppgaver som autentisering, autorisasjon, logging og omdirigering av brukere basert på deres plassering eller enhet.
Middleware defineres i en fil som heter `middleware.js` (eller `middleware.ts`) i roten av prosjektet ditt.
Eksempel:
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
// Sjekk om brukeren er autentisert
const isAuthenticated = false; // Erstatt med din autentiseringslogikk
if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// Se "Matching Paths" nedenfor for å lære mer
export const config = {
matcher: '/admin/:path*',
}
Dette eksempelet definerer middleware som sjekker om brukeren er autentisert før den lar dem få tilgang til noen rute under `/admin`. Hvis brukeren ikke er autentisert, blir de omdirigert til `/login`-siden.
Beste Praksis for Filbasert Ruting
For å få mest mulig ut av App Routerens filbaserte rutingsystem, bør du vurdere følgende beste praksis:
- Hold filstrukturen organisert: Bruk meningsfylte mappenavn og grupper relaterte filer sammen.
- Bruk layouter for delt UI: Lag layouter for headere, footere, sidebarer og andre elementer som deles på tvers av flere sider.
- Bruk laste-UI-er: Gi laste-UI-er for ruter som henter data eller utfører andre asynkrone operasjoner.
- Håndter feil elegant: Lag tilpassede feil-UI-er for å gi en bedre brukeropplevelse når feil oppstår.
- Bruk rutegrupper for organisering: Bruk rutegrupper for å organisere rutene dine uten å påvirke URL-strukturen.
- Utnytt serverkomponenter for ytelse: Bruk serverkomponenter for å hente data og rendre UI på serveren, noe som forbedrer ytelse og SEO.
- Bruk klientkomponenter når det er nødvendig: Bruk klientkomponenter når du trenger å bruke klient-side funksjoner som hendelseslyttere, tilstand og nettleser-API-er.
Eksempler på Internasjonalisering med Next.js App Router
Next.js App Router forenkler internasjonalisering (i18n) gjennom filbasert ruting. Slik kan du implementere i18n effektivt:
1. Sub-path Ruting
Organiser rutene dine basert på locale ved hjelp av sub-paths. For eksempel:
app/
[locale]/
page.tsx // Hjemmeside for locale
about/
page.tsx // Om-side for locale
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';
export default async function HomePage({ params: { locale } }) {
const t = await getTranslations(locale);
return (<h1>{t.home.title}</h1>);
}
// dictionaries.js
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
es: () => import('./dictionaries/es.json').then((module) => module.default),
};
export const getTranslations = async (locale) => {
try {
return dictionaries[locale]() ?? dictionaries.en();
} catch (error) {
console.error(`Klarte ikke å laste oversettelser for locale ${locale}`, error);
return dictionaries.en();
}
};
I dette oppsettet håndterer det dynamiske rutesegmentet `[locale]` forskjellige locales (f.eks. `/en`, `/no`). Oversettelsene lastes dynamisk basert på locale.
2. Domeneruting
For en mer avansert tilnærming kan du bruke forskjellige domener eller underdomener for hver locale. Dette innebærer ofte ekstra konfigurasjon med din hostingleverandør.
3. Middleware for Locale-deteksjon
Bruk middleware for automatisk å oppdage brukerens foretrukne locale og omdirigere dem deretter.
// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
let locales = ['en', 'no', 'fr'];
function getLocale(request) {
const negotiatorHeaders = {};
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
let languages = new Negotiator({ headers: negotiatorHeaders }).languages();
try {
return match(languages, locales, 'en'); // Bruk "en" som standard locale
} catch (error) {
console.error("Feil ved matching av locale:", error);
return 'en'; // Fallback til engelsk hvis matching feiler
}
}
export function middleware(request) {
const pathname = request.nextUrl.pathname;
const pathnameIsMissingLocale = locales.every(
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);
if (pathnameIsMissingLocale) {
const locale = getLocale(request);
return NextResponse.redirect(
new URL(
`/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
request.url
)
);
}
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
Denne middleware sjekker om den forespurte stien har et locale-prefiks. Hvis ikke, oppdager den brukerens foretrukne locale ved hjelp av `Accept-Language`-headeren og omdirigerer dem til den passende locale-spesifikke stien. Biblioteker som `@formatjs/intl-localematcher` og `negotiator` brukes til å håndtere locale-forhandling.
Next.js App Router og Global Tilgjengelighet
Å lage globalt tilgjengelige webapplikasjoner krever nøye vurdering av tilgjengelighetsprinsipper (a11y). Next.js App Router gir et solid fundament for å bygge tilgjengelige opplevelser, men det er viktig å implementere beste praksis for å sikre at applikasjonen din er brukbar for alle, uavhengig av deres evner.
Viktige Tilgjengelighetshensyn
- Semantisk HTML: Bruk semantiske HTML-elementer (f.eks. `<article>`, `<nav>`, `<aside>`, `<main>`) for å strukturere innholdet ditt. Dette gir mening til hjelpemidler og hjelper brukere med å navigere på nettstedet ditt enklere.
- ARIA-attributter: Bruk ARIA (Accessible Rich Internet Applications)-attributter for å forbedre tilgjengeligheten til tilpassede komponenter og widgets. ARIA-attributter gir tilleggsinformasjon om rollen, tilstanden og egenskapene til elementer for hjelpemidler.
- Tastaturnavigasjon: Sørg for at alle interaktive elementer er tilgjengelige via tastaturet. Brukere skal kunne navigere gjennom applikasjonen din med `Tab`-tasten og samhandle med elementer med `Enter`- eller `Mellomrom`-tasten.
- Fargekontrast: Bruk tilstrekkelig fargekontrast mellom tekst og bakgrunn for å sikre lesbarhet for brukere med synshemninger. Web Content Accessibility Guidelines (WCAG) anbefaler et kontrastforhold på minst 4.5:1 for normal tekst og 3:1 for stor tekst.
- Alternativ tekst for bilder: Gi beskrivende alternativ tekst (alt-tekst) for alle bilder. Alt-tekst gir et tekstalternativ for bilder som kan leses av skjermlesere.
- Skjemaetiketter: Knytt skjemaetiketter til deres korresponderende inndatafelt ved hjelp av `<label>`-elementet. Dette gjør det klart for brukere hvilken informasjon som forventes i hvert felt.
- Skjermlesertesting: Test applikasjonen din med en skjermleser for å sikre at den er tilgjengelig for brukere med synshemninger. Populære skjermlesere inkluderer NVDA, JAWS og VoiceOver.
Implementere Tilgjengelighet i Next.js App Router
- Bruk Next.js Link-komponenten: Bruk `<Link>`-komponenten for navigasjon. Den gir innebygde tilgjengelighetsfunksjoner, som prefetching og fokushåndtering.
- Fokushåndtering: Når du navigerer mellom sider eller åpner modaler, sørg for at fokuset håndteres riktig. Fokuset bør settes til det mest logiske elementet på den nye siden eller modalen.
- Tilgjengelige Tilpassede Komponenter: Når du lager tilpassede komponenter, sørg for at de er tilgjengelige ved å følge prinsippene beskrevet ovenfor. Bruk semantisk HTML, ARIA-attributter og tastaturnavigasjon for å gjøre komponentene dine brukbare for alle.
- Linting og Testing: Bruk linting-verktøy som ESLint med tilgjengelighetsplugins for å identifisere potensielle tilgjengelighetsproblemer i koden din. Bruk også automatiserte testverktøy for å teste applikasjonen din for tilgjengelighetsbrudd.
Konklusjon
Next.js App Router sitt filbaserte rutingsystem tilbyr en kraftig og intuitiv måte å strukturere og navigere i applikasjonene dine på. Ved å forstå kjernekonseptene og beste praksis som er beskrevet i denne guiden, kan du bygge robuste, skalerbare og vedlikeholdbare Next.js-applikasjoner. Eksperimenter med de forskjellige funksjonene i App Router og oppdag hvordan den kan forenkle utviklingsflyten din og forbedre brukeropplevelsen.