Svenska

Lås upp kraften i Next.js App Router med vår djupgående guide till filbaserad routing. Lär dig att strukturera din applikation, skapa dynamiska rutter, hantera layouter och mer.

Next.js App Router: En Omfattande Guide till Filbaserad Routing

Next.js App Router, som introducerades i Next.js 13 och blev standard i senare versioner, revolutionerar hur vi strukturerar och navigerar i applikationer. Den introducerar ett kraftfullt och intuitivt filbaserat routingsystem som förenklar utveckling, förbättrar prestanda och förstärker den övergripande utvecklarupplevelsen. Denna omfattande guide kommer att djupdyka i App Routers filbaserade routing och ge dig kunskapen och färdigheterna för att bygga robusta och skalbara Next.js-applikationer.

Vad är Filbaserad Routing?

Filbaserad routing är ett routingsystem där strukturen på din applikations rutter direkt bestäms av organiseringen av dina filer och kataloger. I Next.js App Router definierar du rutter genom att skapa filer i `app`-katalogen. Varje mapp representerar ett ruttsegment, och speciella filer inom dessa mappar definierar hur det ruttsegmentet ska hanteras. Detta tillvägagångssätt erbjuder flera fördelar:

Kom igång med App Router

För att använda App Router behöver du skapa ett nytt Next.js-projekt eller migrera ett befintligt. Se till att du använder Next.js version 13 eller senare.

Skapa ett nytt projekt:

Du kan skapa ett nytt Next.js-projekt med App Router med följande kommando:

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

Migrera ett befintligt projekt:

För att migrera ett befintligt projekt måste du flytta dina sidor från `pages`-katalogen till `app`-katalogen. Du kan behöva justera din routinglogik i enlighet med detta. Next.js tillhandahåller en migreringsguide för att hjälpa dig med denna process.

Kärnkoncept för Filbaserad Routing

App Router introducerar flera speciella filer och konventioner som definierar hur dina rutter hanteras:

1. `app`-katalogen

`app`-katalogen är roten för din applikations rutter. Alla filer och mappar inom denna katalog kommer att användas för att generera rutter. Allt utanför `app`-katalogen (som `pages`-katalogen om du migrerar) kommer att ignoreras av App Router.

2. `page.js`-filen

Filen `page.js` (eller `page.jsx`, `page.ts`, `page.tsx`) är den mest grundläggande delen av App Router. Den definierar UI-komponenten som kommer att renderas för ett specifikt ruttsegment. Det är en **obligatorisk** fil för varje ruttsegment du vill ska vara direkt åtkomligt.

Exempel:

Om du har en filstruktur som denna:

app/
  about/
    page.js

Komponenten som exporteras från `app/about/page.js` kommer att renderas när en användare navigerar till `/about`.

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

export default function AboutPage() {
  return (
    <div>
      <h1>Om oss</h1>
      <p>Lär dig mer om vårt företag.</p>
    </div>
  );
}

3. `layout.js`-filen

Filen `layout.js` (eller `layout.jsx`, `layout.ts`, `layout.tsx`) definierar ett UI som delas mellan flera sidor inom ett ruttsegment. Layouter är användbara för att skapa konsekventa sidhuvuden, sidfötter, sidofält och andra element som ska finnas på flera sidor.

Exempel:

Låt oss säga att du vill lägga till ett sidhuvud på både `/about`-sidan och en hypotetisk `/about/team`-sida. Du kan skapa en `layout.js`-fil i `app/about`-katalogen:

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

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>Om vårt företag</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

`children`-propen kommer att ersättas med UI:t som renderas av `page.js`-filen i samma katalog eller i eventuella nästlade kataloger.

4. `template.js`-filen

Filen `template.js` liknar `layout.js`, men den skapar en ny instans av komponenten för varje underordnad rutt. Detta är användbart för scenarier där du vill bibehålla komponentens tillstånd eller förhindra omrenderingar när du navigerar mellan underordnade rutter. Till skillnad från layouter kommer mallar att omrenderas vid navigering. Att använda mallar är utmärkt för att animera element vid navigering.

Exempel:

// app/template.js
'use client'

import { useState } from 'react'

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

  return (
    <main>
      <p>Mall: {count}</p>
      <button onClick={() => setCount(count + 1)}>Uppdatera mall</button>
      {children}
    </main>
  )
}

5. `loading.js`-filen

Filen `loading.js` (eller `loading.jsx`, `loading.ts`, `loading.tsx`) låter dig skapa ett laddnings-UI som visas medan ett ruttsegment laddas. Detta är användbart för att ge en bättre användarupplevelse vid datahämtning eller andra asynkrona operationer.

Exempel:

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

export default function Loading() {
  return <p>Laddar information om oss...</p>;
}

När en användare navigerar till `/about` kommer `Loading`-komponenten att visas tills `page.js`-komponenten är fullständigt renderad.

6. `error.js`-filen

Filen `error.js` (eller `error.jsx`, `error.ts`, `error.tsx`) låter dig skapa ett anpassat fel-UI som visas när ett fel uppstår inom ett ruttsegment. Detta är användbart för att ge ett mer användarvänligt felmeddelande och förhindra att hela applikationen kraschar.

Exempel:

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

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Ett fel inträffade!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Försök igen</button>
    </div>
  );
}

Om ett fel uppstår när `/about`-sidan renderas, kommer `Error`-komponenten att visas. `error`-propen innehåller information om felet, och `reset`-funktionen låter användaren försöka ladda om sidan.

7. Ruttgrupper

Ruttgrupper `(gruppnamn)` låter dig organisera dina rutter utan att påverka URL-strukturen. De skapas genom att omsluta ett mappnamn med parenteser. Detta är särskilt användbart för att organisera layouter och delade komponenter.

Exempel:

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

I detta exempel är `about`- och `contact`-sidorna grupperade under `marketing`-gruppen, och `products`-sidan är under `shop`-gruppen. URL:erna förblir `/about`, `/contact` och `/products`.

8. Dynamiska rutter

Dynamiska rutter låter dig skapa rutter med variabla segment. Detta är användbart för att visa innehåll baserat på data hämtad från en databas eller ett API. Dynamiska ruttsegment definieras genom att omsluta segmentnamnet med hakparenteser (t.ex. `[id]`)

Exempel:

Låt oss säga att du vill skapa en rutt för att visa enskilda blogginlägg baserat på deras ID. Du kan skapa en filstruktur som denna:

app/
  blog/
    [id]/
      page.js

`[id]`-segmentet är ett dynamiskt segment. Komponenten som exporteras från `app/blog/[id]/page.js` kommer att renderas när en användare navigerar till en URL som `/blog/123` eller `/blog/456`. Värdet på `id`-parametern kommer att finnas tillgängligt i `params`-propen i komponenten.

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

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

  // Hämta data för blogginlägget med det givna ID:t
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Blogginlägget hittades inte.</p>;
  }

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

async function fetchBlogPost(id) {
  // Simulera hämtning av data från en databas eller ett API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Mitt första blogginlägg', content: 'Detta är innehållet i mitt första blogginlägg.' },
        '456': { title: 'Ännu ett blogginlägg', content: 'Här är lite mer spännande innehåll.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Du kan också använda flera dynamiska segment i en rutt. Till exempel kan du ha en rutt som `/blog/[category]/[id]`.

9. Catch-all-segment

Catch-all-segment (även kallat samlingssegment) låter dig skapa rutter som matchar ett valfritt antal segment. Detta är användbart för scenarier som att skapa ett CMS där URL-strukturen bestäms av användaren. Catch-all-segment definieras genom att lägga till tre punkter före segmentnamnet (t.ex. `[...slug]`)

Exempel:

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

`[...slug]`-segmentet kommer att matcha ett valfritt antal segment efter `/docs`. Till exempel kommer det att matcha `/docs/getting-started`, `/docs/api/users` och `/docs/advanced/configuration`. Värdet på `slug`-parametern kommer att vara en array som innehåller de matchade segmenten.

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

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

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

Valfria catch-all-segment kan skapas genom att lägga till segmentnamnet inom dubbla hakparenteser `[[...slug]]`. Detta gör ruttsegmentet valfritt. Exempel:

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

Denna konfiguration kommer att rendera page.js-komponenten både på `/blog` och `/blog/any/number/of/segments`.

10. Parallella rutter

Parallella rutter låter dig rendera en eller flera sidor samtidigt i samma layout. Detta är särskilt användbart för komplexa layouter, som instrumentpaneler, där olika delar av sidan kan laddas oberoende av varandra. Parallella rutter definieras med `@`-symbolen följt av ett slot-namn (t.ex. `@sidebar`, `@main`).

Exempel:

app/
  @sidebar/
    page.js  // Innehåll för sidofältet
  @main/
    page.js  // Innehåll för huvudsektionen
  default.js // Krävs: Definierar standardlayouten för parallella rutter

Filen `default.js` krävs när man använder parallella rutter. Den definierar hur de olika slot-komponenterna kombineras för att skapa den slutliga 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. Avlyssnande rutter (Intercepting Routes)

Avlyssnande rutter låter dig ladda en rutt från en annan del av din applikation inom den nuvarande layouten. Detta är användbart för att skapa modaler, bildgallerier och andra UI-element som ska visas ovanpå det befintliga sidinnehållet. Avlyssnande rutter definieras med `(..)`-syntaxen, som indikerar hur många nivåer upp i katalogträdet man ska gå för att hitta den avlyssnade rutten.

Exempel:

app/
  (.)photos/
    [id]/
      page.js  // Den avlyssnade rutten
  feed/
    page.js  // Sidan där fotomodalen visas

I detta exempel, när en användare klickar på ett foto på `/feed`-sidan, avlyssnas `app/(.)photos/[id]/page.js`-rutten och visas som en modal ovanpå `/feed`-sidan. `(.)`-syntaxen talar om för Next.js att titta en nivå upp (till `app`-katalogen) för att hitta `photos/[id]`-rutten.

Datahämtning med App Router

App Router erbjuder inbyggt stöd för datahämtning med hjälp av Serverkomponenter och Klientkomponenter. Serverkomponenter renderas på servern, medan Klientkomponenter renderas på klienten. Detta låter dig välja den bästa metoden för varje komponent baserat på dess krav.

Serverkomponenter

Serverkomponenter är standard i App Router. De låter dig hämta data direkt i dina komponenter utan behov av separata API-rutter. Detta kan förbättra prestandan och förenkla din kod.

Exempel:

// 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() {
  // Simulera hämtning av data från en databas eller ett 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 detta exempel anropas `fetchProducts`-funktionen direkt inuti `ProductsPage`-komponenten. Komponenten renderas på servern, och datan hämtas innan HTML-koden skickas till klienten.

Klientkomponenter

Klientkomponenter renderas på klienten och låter dig använda klientsidiga funktioner som händelselyssnare, tillstånd (state) och webbläsar-API:er. För att använda en Klientkomponent måste du lägga till direktivet `'use client'` högst upp i filen.

Exempel:

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Räknare</h1>
      <p>Antal: {count}</p>
      <button onClick={() => setCount(count + 1)}>Öka</button>
    </div>
  );
}

I detta exempel är `CounterPage`-komponenten en Klientkomponent eftersom den använder `useState`-hooken. Direktivet `'use client'` talar om för Next.js att rendera denna komponent på klienten.

Avancerade Routing-tekniker

App Router erbjuder flera avancerade routing-tekniker som kan användas för att skapa komplexa och sofistikerade applikationer.

1. Rutthanterare (Route Handlers)

Rutthanterare låter dig skapa API-slutpunkter inom din `app`-katalog. Detta eliminerar behovet av en separat `pages/api`-katalog. Rutthanterare definieras i filer som heter `route.js` (eller `route.ts`) och exporterar funktioner som hanterar olika HTTP-metoder (t.ex. `GET`, `POST`, `PUT`, `DELETE`).

Exempel:

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // Simulera hämtning av användare från en databas
  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('Mottagen data:', body)
  return NextResponse.json({ message: 'Användare skapad' }, { status: 201 })
}

Detta exempel definierar en rutthanterare på `/api/users` som hanterar både `GET`- och `POST`-förfrågningar. `GET`-funktionen returnerar en lista med användare, och `POST`-funktionen skapar en ny användare.

2. Ruttgrupper med flera layouter

Du kan kombinera ruttgrupper med layouter för att skapa olika layouter för olika delar av din applikation. Detta är användbart för scenarier där du vill ha ett annat sidhuvud eller sidofält för olika delar av din webbplats.

Exempel:

app/
  (marketing)/
    layout.js  // Marknadsföringslayout
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Adminlayout
    dashboard/
      page.js

I detta exempel kommer `about`- och `contact`-sidorna att använda `marketing`-layouten, medan `dashboard`-sidan kommer att använda `admin`-layouten.

3. Middleware

Middleware låter dig köra kod innan en förfrågan hanteras av din applikation. Detta är användbart för uppgifter som autentisering, auktorisering, loggning och omdirigering av användare baserat på deras plats eller enhet.

Middleware definieras i en fil som heter `middleware.js` (eller `middleware.ts`) i roten av ditt projekt.

Exempel:

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

export function middleware(request) {
  // Kontrollera om användaren är autentiserad
  const isAuthenticated = false; // Ersätt med din autentiseringslogik

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

  return NextResponse.next();
}

// Se "Matching Paths" nedan för att lära dig mer
export const config = {
  matcher: '/admin/:path*',
}

Detta exempel definierar middleware som kontrollerar om användaren är autentiserad innan den tillåts komma åt någon rutt under `/admin`. Om användaren inte är autentiserad, omdirigeras de till `/login`-sidan.

Bästa praxis för Filbaserad Routing

För att få ut det mesta av App Routers filbaserade routingsystem, överväg följande bästa praxis:

Exempel på internationalisering med Next.js App Router

Next.js App Router förenklar internationalisering (i18n) genom filbaserad routing. Så här kan du implementera i18n effektivt:

1. Sub-path Routing

Organisera dina rutter baserat på språk med hjälp av underordnade sökvägar. Till exempel:

app/
  [locale]/
    page.tsx         // Hemsida för språket
    about/
      page.tsx     // Om-sida för språket
// 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),
  sv: () => import('./dictionaries/sv.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`Misslyckades att ladda översättningar för språket ${locale}`, error);
    return dictionaries.en();
  }
};

I denna konfiguration hanterar det dynamiska ruttsegmentet `[locale]` olika språk (t.ex. `/en`, `/sv`). Översättningarna laddas dynamiskt baserat på språket.

2. Domänrouting

För en mer avancerad metod kan du använda olika domäner eller subdomäner för varje språk. Detta innebär ofta ytterligare konfiguration hos din hostingleverantör.

3. Middleware för språkdetektering

Använd middleware för att automatiskt upptäcka användarens föredragna språk och omdirigera dem därefter.

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

let locales = ['en', 'sv', '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, 'sv'); // Använd "sv" som standardspråk
  } catch (error) {
      console.error("Fel vid matchning av språk:", error);
      return 'sv'; // Återgå till svenska om matchning misslyckas
  }
}

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).*)',
  ],
};

Denna middleware kontrollerar om den begärda sökvägen har ett språkprefix. Om inte, upptäcker den användarens föredragna språk med hjälp av `Accept-Language`-huvudet och omdirigerar dem till den lämpliga språkspecifika sökvägen. Bibliotek som `@formatjs/intl-localematcher` och `negotiator` används för att hantera språkförhandling.

Next.js App Router och global tillgänglighet

Att skapa globalt tillgängliga webbapplikationer kräver noggrann hänsyn till tillgänglighetsprinciper (a11y). Next.js App Router utgör en solid grund för att bygga tillgängliga upplevelser, men det är avgörande att implementera bästa praxis för att säkerställa att din applikation är användbar för alla, oavsett deras förmågor.

Viktiga tillgänglighetsaspekter

  1. Semantisk HTML: Använd semantiska HTML-element (t.ex. `<article>`, `<nav>`, `<aside>`, `<main>`) för att strukturera ditt innehåll. Detta ger mening till hjälpmedelstekniker och hjälper användare att navigera på din webbplats enklare.
  2. ARIA-attribut: Använd ARIA-attribut (Accessible Rich Internet Applications) för att förbättra tillgängligheten för anpassade komponenter och widgets. ARIA-attribut ger ytterligare information om roll, tillstånd och egenskaper för element till hjälpmedelstekniker.
  3. Tangentbordsnavigering: Se till att alla interaktiva element är tillgängliga via tangentbord. Användare ska kunna navigera genom din applikation med `Tab`-tangenten och interagera med element med `Enter`- eller `Mellanslag`-tangenten.
  4. Färgkontrast: Använd tillräcklig färgkontrast mellan text och bakgrund för att säkerställa läsbarhet för användare med synnedsättningar. Web Content Accessibility Guidelines (WCAG) rekommenderar ett kontrastförhållande på minst 4.5:1 för normal text och 3:1 för stor text.
  5. Alt-text för bilder: Tillhandahåll beskrivande alt-text för alla bilder. Alt-text ger ett textalternativ för bilder som kan läsas av skärmläsare.
  6. Formuläretiketter: Koppla formuläretiketter till deras motsvarande inmatningsfält med `<label>`-elementet. Detta gör det tydligt för användare vilken information som förväntas i varje fält.
  7. Testning med skärmläsare: Testa din applikation med en skärmläsare för att säkerställa att den är tillgänglig för användare med synnedsättningar. Populära skärmläsare inkluderar NVDA, JAWS och VoiceOver.

Implementera tillgänglighet i Next.js App Router

  1. Använd Next.js Link-komponent: Använd `<Link>`-komponenten för navigering. Den tillhandahåller inbyggda tillgänglighetsfunktioner, som förhandsladdning (prefetching) och fokushantering.
  2. Fokushantering: När du navigerar mellan sidor eller öppnar modaler, se till att fokus hanteras korrekt. Fokus bör sättas på det mest logiska elementet på den nya sidan eller i modalen.
  3. Tillgängliga anpassade komponenter: När du skapar anpassade komponenter, se till att de är tillgängliga genom att följa principerna ovan. Använd semantisk HTML, ARIA-attribut och tangentbordsnavigering för att göra dina komponenter användbara för alla.
  4. Linting och testning: Använd linting-verktyg som ESLint med tillgänglighetsplugins för att identifiera potentiella tillgänglighetsproblem i din kod. Använd också automatiserade testverktyg för att testa din applikation för tillgänglighetsöverträdelser.

Slutsats

Next.js App Routers filbaserade routingsystem erbjuder ett kraftfullt och intuitivt sätt att strukturera och navigera i dina applikationer. Genom att förstå de kärnkoncept och bästa praxis som beskrivs i denna guide kan du bygga robusta, skalbara och underhållsbara Next.js-applikationer. Experimentera med de olika funktionerna i App Router och upptäck hur det kan förenkla ditt arbetsflöde och förbättra användarupplevelsen.