Layouts, które pojawiło się w Next 13 znacząco zmieni sposób w jaki tworzymy aplikacje. Jeśli chcesz się dowiedzieć dlaczego to zostało wprowadzone, jakie rozwiązuje problemy i jak z tego skorzystać, to czytaj dalej.
Do tej pory routing w aplikacjach next.js, był oparty o pliki. Każdy plik był osobną ścieżką w aplikacji. Przypominało to trochę sposób w jaki tworzyliśmy strony html, czyli jeden adres jest rozwiązywany do jednego pliku. Powodowało to, że jeśli mieliśmy jakieś wspólne elementy pomiędzy stronami, to musieliśmy to albo duplikować albo wynosić do zewnętrznego kodu. Było to dosyć niewygodne gdy mieliśmy zagnieżdżone elementy np. w aplikacji typu dashboard gdzie bardzo często mamy menu z lewej strony i zmienia się tylko treść po prawej.
Layouts działają w sposób jaki znamy od lat np. w React jeśli korzystamy z react-router, czyli możliwość zagnieżdżania widoków w sobie. Oznacza to że przechodząc między stronami nie renderuje się cała strona od nowa, tylko część która się aktualnie zmieniła. W ten sam sposób działa również Remix.
Co warto wiedzieć:
Żeby zacząć korzystać z nowej funkcjonalności musimy skorzystać z folderu app. Folder pages cały czas zostaje dzięki czemu możemy stopniowo przechodzić do Layouts.
Wewnątrz app:
Specjalne pliki:
'use client';
na górze komponentu),Cały kod znajdziesz na Github, a poniżej masz krótkie demo.
To jest ciągle funkcjonalność w wersji beta i trzeba mieć to na uwadze. Żeby aktywować tę funkcjonalność musisz w pliku next.config.js dodać poniższą konfigurację.
const nextConfig = {
// reszta konfiguracji
experimental:{
appDir: true
}
}
Ja też usunąłem folder pages ponieważ były konflikty pomiędzy starym a nowym routingiem.
Tutaj postawiłem na minimalizm i mam tylko wymagane pliki.
// app/pages.tsx
import Link from "next/link";
export default function Page() {
return (<>
<h1>Hello, Next.js 13!</h1>
<Link href={`dashboard`}>Go to dashboard</Link>
</>);
}
// app/layout.tsx
/* eslint-disable @next/next/no-head-element */
export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html>
<head></head>
<body>{children}</body>
</html>
);
}
Jak wspomniałem wyżej plik layout dla roota jest obowiązkowy i Next sam mi wygenerował ten plik, gdy o tym zapomniałem.
Tutaj najciekawszy jest plik layout.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children}: {children: React.ReactNode;}) {
const fields = [
{ name: "Long", slug: "long" },
{ name: "Quick", slug: "quick" },
{ name: "Parallel", slug: "parallel" },
];
return (
<>
<div className="menu">
{fields.map((f) => (
<Link href={`dashboard/${f.slug}`} style={{ margin: 16 }}>
{f.name}
</Link>
))}
</div>
<section>{children}</section>
</>
);
}
Mam tutaj typowy przykład dashboardu, czyli sytuacji gdzie z lewej strony mamy statyczne menu a po prawej zmieniającą się treść. Dzięki wykorzystaniu Layouts to się nie będzie renderować.
// app/dashboard/long/page.tsx
async function getData() {
await new Promise((r) => setTimeout(r, 5 * 1000));
return "long";
}
export default async function Page() {
const data = await getData();
return (
<>
<h1>Hello, {data}</h1>
</>);
}
Jako ścieżkę long dałem sztuczny promise. Warto zwrócić uwagę na to, że jest to komponent asynchroniczny. Dzięki temu można dodać loading.
// app/dashboard/long/loading.tsx
export default function Loading() {
return <h2>loading long</h2>
}
Z fajnych rzeczy to jeszcze jest możliwość ładowania komponentów niezależnie. Mamy tę możliwość dzięki wykorzystaniu React.Suspense.
import { Suspense } from "react";
async function getData1() {
await new Promise((r) => setTimeout(r, 4 * 1000));
return 1;
}
async function getData2() {
await new Promise((r) => setTimeout(r, 1 * 1000));
return 2;
}
async function getData3() {
await new Promise((r) => setTimeout(r, 3 * 1000));
return 3;
}
async function LongFetch({ promise }) {
const data = await promise;
return <h2>Loaded long fetch {data}</h2>;
}
export default async function Page() {
const fields1 = getData1();
const fields2 = getData2();
const fields3 = getData3();
return (
<>
<h1>It work parallel</h1>
<Suspense fallback={<div>Loading section 1....</div>}>
<LongFetch promise={fields1} />
</Suspense>
<Suspense fallback={<div>Loading section 2....</div>}>
<LongFetch promise={fields2} />
</Suspense>
<Suspense fallback={<div>Loading section 3....</div>}>
<LongFetch promise={fields3} />
</Suspense>
</>);
}
Mamy 3 komponenty, które pokazują się w różnym czasie. Każdy z nich ma niezależny loader co widać na gifie na początku.
Layouts nie są jedyną zmianą którą wprowadza Next13. Z tych ciekawszych warto wymienić:
<a>
jako dziecka.