Dansk

Frigør kraften i Next.js App Router med vores dybdegående guide til filbaseret routing. Lær at strukturere din applikation, oprette dynamiske ruter, håndtere layouts og meget mere.

Next.js App Router: En Omfattende Guide til Filbaseret Routing

Next.js App Router, introduceret i Next.js 13 og standard i senere versioner, revolutionerer måden, vi strukturerer og navigerer i applikationer. Den introducerer et kraftfuldt og intuitivt filbaseret routing-system, der forenkler udvikling, forbedrer ydeevnen og løfter den samlede udvikleroplevelse. Denne omfattende guide vil dykke dybt ned i App Routerens filbaserede routing og give dig den viden og de færdigheder, der skal til for at bygge robuste og skalerbare Next.js-applikationer.

Hvad er Filbaseret Routing?

Filbaseret routing er et system, hvor strukturen af din applikations ruter direkte bestemmes af organiseringen af dine filer og mapper. I Next.js App Router definerer du ruter ved at oprette filer i `app`-mappen. Hver mappe repræsenterer et rutesegment, og specielle filer i disse mapper definerer, hvordan det pågældende rutesegment skal håndteres. Denne tilgang giver flere fordele:

Kom i gang med App Router

For at bruge App Routeren skal du oprette et nyt Next.js-projekt eller migrere et eksisterende. Sørg for, at du bruger Next.js version 13 eller nyere.

Oprettelse af et nyt projekt:

Du kan oprette et nyt Next.js-projekt med App Router ved hjælp af følgende kommando:

npx create-next-app@latest my-app --example with-app

Migrering af et eksisterende projekt:

For at migrere et eksisterende projekt skal du flytte dine sider fra `pages`-mappen til `app`-mappen. Du skal muligvis justere din routing-logik i overensstemmelse hermed. Next.js tilbyder en migrationsguide for at hjælpe dig med denne proces.

Kernekoncepter i Filbaseret Routing

App Routeren introducerer flere specielle filer og konventioner, der definerer, hvordan dine ruter håndteres:

1. `app`-mappen

`app`-mappen er roden for din applikations ruter. Alle filer og mapper i denne mappe vil blive brugt til at generere ruter. Alt uden for `app`-mappen (som `pages`-mappen, hvis du migrerer) vil blive ignoreret af App Routeren.

2. `page.js`-filen

`page.js` (eller `page.jsx`, `page.ts`, `page.tsx`)-filen er den mest fundamentale del af App Routeren. Den definerer UI-komponenten, der vil blive renderet for et specifikt rutesegment. Det er en **nødvendig** fil for ethvert rutesegment, du ønsker skal være direkte tilgængeligt.

Eksempel:

Hvis du har en filstruktur som denne:

app/
  about/
    page.js

Komponenten, der eksporteres fra `app/about/page.js`, vil blive renderet, når en bruger navigerer til `/about`.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>Om Os</h1>
      <p>Lær mere om vores virksomhed.</p>
    </div>
  );
}

3. `layout.js`-filen

`layout.js` (eller `layout.jsx`, `layout.ts`, `layout.tsx`)-filen definerer en brugergrænseflade, der deles på tværs af flere sider inden for et rutesegment. Layouts er nyttige til at skabe ensartede sidehoveder, sidefødder, sidebjælker og andre elementer, der skal være til stede på flere sider.

Eksempel:

Lad os sige, du vil tilføje et sidehoved til både `/about`-siden og en hypotetisk `/about/team`-side. Du kan oprette 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 Vores Virksomhed</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

`children`-proppen vil blive erstattet med den UI, der renderes af `page.js`-filen i samme mappe eller i eventuelle undermapper.

4. `template.js`-filen

`template.js`-filen ligner `layout.js`, men den opretter en ny instans af komponenten for hver underordnet rute. Dette er nyttigt i scenarier, hvor du vil bevare komponentens tilstand eller forhindre re-renders, når du navigerer mellem underordnede ruter. I modsætning til layouts vil templates re-rendere ved navigation. Brug af templates er fantastisk til at animere elementer ved navigation.

Eksempel:

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>Template: {count}</p>
      <button onClick={() => setCount(count + 1)}>Opdater Template</button>
      {children}
    </main>
  )
}

5. `loading.js`-filen

`loading.js` (eller `loading.jsx`, `loading.ts`, `loading.tsx`)-filen giver dig mulighed for at oprette en indlæsnings-UI, der vises, mens et rutesegment indlæses. Dette er nyttigt for at give en bedre brugeroplevelse, når der hentes data eller udføres andre asynkrone operationer.

Eksempel:

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>Indlæser information om os...</p>;
}

Når en bruger navigerer til `/about`, vil `Loading`-komponenten blive vist, indtil `page.js`-komponenten er fuldt renderet.

6. `error.js`-filen

`error.js` (eller `error.jsx`, `error.ts`, `error.tsx`)-filen giver dig mulighed for at oprette en brugerdefineret fejl-UI, der vises, når der opstår en fejl inden for et rutesegment. Dette er nyttigt for at give en mere brugervenlig fejlmeddelelse og forhindre, at hele applikationen går ned.

Eksempel:

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Der opstod en fejl!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Prøv igen</button>
    </div>
  );
}

Hvis der opstår en fejl under renderingen af `/about`-siden, vil `Error`-komponenten blive vist. `error`-proppen indeholder information om fejlen, og `reset`-funktionen giver brugeren mulighed for at forsøge at genindlæse siden.

7. Rutegrupper

Rutegrupper `(groupName)` giver dig mulighed for at organisere dine ruter uden at påvirke URL-strukturen. De oprettes ved at pakke et mappenavn ind i parenteser. Dette er især nyttigt til at organisere layouts og delte komponenter.

Eksempel:

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

I dette eksempel er `about`- og `contact`-siderne grupperet under `marketing`-gruppen, og `products`-siden er under `shop`-gruppen. URL'erne forbliver henholdsvis `/about`, `/contact` og `/products`.

8. Dynamiske Ruter

Dynamiske ruter giver dig mulighed for at oprette ruter med variable segmenter. Dette er nyttigt til at vise indhold baseret på data hentet fra en database eller API. Dynamiske rutesegmenter defineres ved at pakke segmentnavnet ind i firkantede parenteser (f.eks. `[id]`).

Eksempel:

Lad os sige, du vil oprette en rute til at vise individuelle blogindlæg baseret på deres ID. Du kan oprette en filstruktur som denne:

app/
  blog/
    [id]/
      page.js

`[id]`-segmentet er et dynamisk segment. Komponenten, der eksporteres fra `app/blog/[id]/page.js`, vil blive renderet, når en bruger navigerer til en URL som `/blog/123` eller `/blog/456`. Værdien af `id`-parameteren vil være tilgængelig i komponentens `params`-prop.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // Hent data for blogindlægget med det givne ID
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Blogindlæg ikke fundet.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // Simulerer hentning af data fra en database eller API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Mit Første Blogindlæg', content: 'Dette er indholdet af mit første blogindlæg.' },
        '456': { title: 'Et Andet Blogindlæg', content: 'Dette er noget mere spændende indhold.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Du kan også bruge flere dynamiske segmenter i en rute. For eksempel kunne du have en rute som `/blog/[category]/[id]`.

9. Catch-all Segmenter

Catch-all segmenter giver dig mulighed for at oprette ruter, der matcher et vilkårligt antal segmenter. Dette er nyttigt til scenarier som at oprette et CMS, hvor URL-strukturen bestemmes af brugeren. Catch-all segmenter defineres ved at tilføje tre prikker før segmentnavnet (f.eks. `[...slug]`).

Eksempel:

app/
  docs/
    [...slug]/
      page.js

`[...slug]`-segmentet vil matche et vilkårligt antal segmenter efter `/docs`. For eksempel vil det matche `/docs/getting-started`, `/docs/api/users` og `/docs/advanced/configuration`. Værdien af `slug`-parameteren vil være en array, der indeholder de matchede segmenter.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>Docs</h1>
      <p>Slug: {slug ? slug.join('/') : 'Intet slug'}</p>
    </div>
  );
}

Valgfri catch-all segmenter kan oprettes ved at tilføje segmentnavnet i dobbelte firkantede parenteser `[[...slug]]`. Dette gør rutesegmentet valgfrit. Eksempel:

app/
  blog/
    [[...slug]]/
      page.js

Denne opsætning vil rendere page.js-komponenten både på `/blog` og `/blog/any/number/of/segments`.

10. Parallelle Ruter

Parallelle Ruter giver dig mulighed for samtidigt at rendere en eller flere sider i det samme layout. Dette er især nyttigt for komplekse layouts, såsom dashboards, hvor forskellige sektioner af siden kan indlæses uafhængigt. Parallelle Ruter defineres ved hjælp af `@`-symbolet efterfulgt af et slot-navn (f.eks. `@sidebar`, `@main`).

Eksempel:

app/
  @sidebar/
    page.js  // Indhold til sidebjælken
  @main/
    page.js  // Indhold til hovedsektionen
  default.js // Nødvendig: Definerer standardlayoutet for parallelle ruter

`default.js`-filen er påkrævet, når man bruger parallelle ruter. Den definerer, hvordan de forskellige slots kombineres for at skabe det endelige layout.

// 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. Intercepting Ruter

Intercepting Ruter giver dig mulighed for at indlæse en rute fra en anden del af din applikation inden for det nuværende layout. Dette er nyttigt til at oprette modaler, billedgallerier og andre UI-elementer, der skal vises oven på det eksisterende sideindhold. Intercepting Ruter defineres ved hjælp af `(..)`-syntaksen, som angiver, hvor mange niveauer op i mappetræet man skal gå for at finde den opsnappede rute.

Eksempel:

app/
  (.)photos/
    [id]/
      page.js  // Den opsnappede rute
  feed/
    page.js  // Siden hvor foto-modalen vises

I dette eksempel, når en bruger klikker på et foto på `/feed`-siden, bliver `app/(.)photos/[id]/page.js`-ruten opsnappet og vist som en modal oven på `/feed`-siden. `(.)`-syntaksen fortæller Next.js at kigge et niveau op (til `app`-mappen) for at finde `photos/[id]`-ruten.

Datahentning med App Router

App Routeren giver indbygget understøttelse for datahentning ved hjælp af Server-komponenter og Klient-komponenter. Server-komponenter renderes på serveren, mens Klient-komponenter renderes på klienten. Dette giver dig mulighed for at vælge den bedste tilgang for hver komponent baseret på dens krav.

Server-komponenter

Server-komponenter er standard i App Routeren. De giver dig mulighed for at hente data direkte i dine komponenter uden behov for separate API-ruter. Dette kan forbedre ydeevnen og forenkle din kode.

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 hentning af 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 eksempel kaldes `fetchProducts`-funktionen direkte inden i `ProductsPage`-komponenten. Komponenten renderes på serveren, og dataene hentes, før HTML'en sendes til klienten.

Klient-komponenter

Klient-komponenter renderes på klienten og giver dig mulighed for at bruge klientside-funktioner som event listeners, state og browser-API'er. For at bruge en Klient-komponent skal du tilføje `'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>Tæller</h1>
      <p>Antal: {count}</p>
      <button onClick={() => setCount(count + 1)}>Forøg</button>
    </div>
  );
}

I dette eksempel er `CounterPage`-komponenten en Klient-komponent, fordi den bruger `useState`-hooket. `'use client'`-direktivet fortæller Next.js, at denne komponent skal renderes på klienten.

Avancerede Routing-teknikker

App Routeren tilbyder flere avancerede routing-teknikker, der kan bruges til at skabe komplekse og sofistikerede applikationer.

1. Route Handlers

Route Handlers giver dig mulighed for at oprette API-endepunkter inden i din `app`-mappe. Dette eliminerer behovet for en separat `pages/api`-mappe. Route Handlers defineres i filer navngivet `route.js` (eller `route.ts`) og eksporterer funktioner, der håndterer forskellige 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 hentning af brugere 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('Modtaget data:', body)
  return NextResponse.json({ message: 'Bruger oprettet' }, { status: 201 })
}

Dette eksempel definerer en route handler på `/api/users`, der håndterer både `GET`- og `POST`-anmodninger. `GET`-funktionen returnerer en liste over brugere, og `POST`-funktionen opretter en ny bruger.

2. Rutegrupper med Flere Layouts

Du kan kombinere rutegrupper med layouts for at skabe forskellige layouts til forskellige sektioner af din applikation. Dette er nyttigt i scenarier, hvor du ønsker at have et forskelligt sidehoved eller sidebjælke for forskellige dele af din side.

Eksempel:

app/
  (marketing)/
    layout.js  // Marketing-layout
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Admin-layout
    dashboard/
      page.js

I dette eksempel vil `about`- og `contact`-siderne bruge `marketing`-layoutet, mens `dashboard`-siden vil bruge `admin`-layoutet.

3. Middleware

Middleware giver dig mulighed for at køre kode, før en anmodning håndteres af din applikation. Dette er nyttigt til opgaver som godkendelse, autorisation, logning og omdirigering af brugere baseret på deres placering eller enhed.

Middleware defineres i en fil navngivet `middleware.js` (eller `middleware.ts`) i roden af dit projekt.

Eksempel:

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Tjek om brugeren er godkendt
  const isAuthenticated = false; // Erstat med din godkendelseslogik

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// Se "Matching Paths" nedenfor for at lære mere
export const config = {
  matcher: '/admin/:path*',
}

Dette eksempel definerer middleware, der tjekker, om brugeren er godkendt, før de får adgang til en rute under `/admin`. Hvis brugeren ikke er godkendt, bliver de omdirigeret til `/login`-siden.

Bedste Praksis for Filbaseret Routing

For at få mest muligt ud af App Routerens filbaserede routing-system, bør du overveje følgende bedste praksis:

Eksempler på Internationalisering med Next.js App Router

Next.js App Router forenkler internationalisering (i18n) gennem filbaseret routing. Her er, hvordan du kan implementere i18n effektivt:

1. Sub-path Routing

Organiser dine ruter baseret på sprogversion (locale) ved hjælp af understier (sub-paths). For eksempel:

app/
  [locale]/
    page.tsx         // Hjemmeside for sprogversionen
    about/
      page.tsx     // Om-side for sprogversionen
// 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(`Kunne ikke indlæse oversættelser for sprogversion ${locale}`, error);
    return dictionaries.en();
  }
};

I denne opsætning håndterer det dynamiske rutesegment `[locale]` forskellige sprogversioner (f.eks. `/en`, `/es`). Oversættelserne indlæses dynamisk baseret på sprogversionen.

2. Domæne-routing

For en mere avanceret tilgang kan du bruge forskellige domæner eller underdomæner for hver sprogversion. Dette indebærer ofte yderligere konfiguration hos din hostingudbyder.

3. Middleware til Sprogdetektering

Brug middleware til automatisk at detektere brugerens foretrukne sprogversion og omdirigere dem i overensstemmelse hermed.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'da', 'de'];

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'); // Brug "en" som standard sprogversion
  } catch (error) {
      console.error("Fejl ved match af sprogversion:", error);
      return 'en'; // Fallback til engelsk hvis match fejler
  }
}

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 tjekker, om den anmodede sti har et sprogpræfiks. Hvis ikke, detekterer den brugerens foretrukne sprog ved hjælp af `Accept-Language`-headeren og omdirigerer dem til den relevante sprogspecifikke sti. Biblioteker som `@formatjs/intl-localematcher` og `negotiator` bruges til at håndtere sprogforhandling.

Next.js App Router og Global Tilgængelighed

At skabe globalt tilgængelige webapplikationer kræver omhyggelig overvejelse af tilgængelighedsprincipper (a11y). Next.js App Router giver et solidt fundament for at bygge tilgængelige oplevelser, men det er afgørende at implementere bedste praksis for at sikre, at din applikation kan bruges af alle, uanset deres evner.

Vigtige Overvejelser om Tilgængelighed

  1. Semantisk HTML: Brug semantiske HTML-elementer (f.eks. `<article>`, `<nav>`, `<aside>`, `<main>`) til at strukturere dit indhold. Dette giver mening for assisterende teknologier og hjælper brugere med at navigere på din side lettere.
  2. ARIA-attributter: Brug ARIA (Accessible Rich Internet Applications)-attributter til at forbedre tilgængeligheden af brugerdefinerede komponenter og widgets. ARIA-attributter giver yderligere information om rollen, tilstanden og egenskaberne for elementer til assisterende teknologier.
  3. Tastaturnavigation: Sørg for, at alle interaktive elementer er tilgængelige via tastaturet. Brugere skal kunne navigere gennem din applikation ved hjælp af `Tab`-tasten og interagere med elementer ved hjælp af `Enter`- eller `Mellemrum`-tasten.
  4. Farvekontrast: Brug tilstrækkelig farvekontrast mellem tekst og baggrund for at sikre læsbarhed for brugere med synshandicap. Retningslinjerne for webtilgængelighed (WCAG) anbefaler et kontrastforhold på mindst 4.5:1 for normal tekst og 3:1 for stor tekst.
  5. Alternativ tekst til billeder: Angiv en beskrivende alternativ tekst (alt text) for alle billeder. Alternativ tekst giver et tekstalternativ til billeder, der kan læses af skærmlæsere.
  6. Formular-etiketter: Forbind formular-etiketter med deres tilsvarende inputfelter ved hjælp af `<label>`-elementet. Dette gør det klart for brugerne, hvilken information der forventes i hvert felt.
  7. Test med skærmlæser: Test din applikation med en skærmlæser for at sikre, at den er tilgængelig for brugere med synshandicap. Populære skærmlæsere inkluderer NVDA, JAWS og VoiceOver.

Implementering af Tilgængelighed i Next.js App Router

  1. Brug Next.js Link-komponenten: Brug `<Link>`-komponenten til navigation. Den giver indbyggede tilgængelighedsfunktioner, såsom prefetching og fokusstyring.
  2. Fokusstyring: Når du navigerer mellem sider eller åbner modaler, skal du sikre, at fokus håndteres korrekt. Fokus bør sættes på det mest logiske element på den nye side eller modal.
  3. Tilgængelige brugerdefinerede komponenter: Når du opretter brugerdefinerede komponenter, skal du sikre, at de er tilgængelige ved at følge de ovennævnte principper. Brug semantisk HTML, ARIA-attributter og tastaturnavigation for at gøre dine komponenter brugbare for alle.
  4. Linting og Testning: Brug linting-værktøjer som ESLint med tilgængeligheds-plugins for at identificere potentielle tilgængelighedsproblemer i din kode. Brug også automatiserede testværktøjer til at teste din applikation for overtrædelser af tilgængelighedsregler.

Konklusion

Next.js App Routerens filbaserede routing-system tilbyder en kraftfuld og intuitiv måde at strukturere og navigere i dine applikationer. Ved at forstå de kernekoncepter og bedste praksis, der er beskrevet i denne guide, kan du bygge robuste, skalerbare og vedligeholdelsesvenlige Next.js-applikationer. Eksperimentér med de forskellige funktioner i App Routeren og opdag, hvordan den kan forenkle din udviklingsproces og forbedre brugeroplevelsen.