Wat is de standaard directorystructuur voor een Next.js-project?
Een standaard directorystructuur voor een Next.js-project is niet één correcte mappenboom die voor elk team identiek geldt. Het betekent eerder dat je vaste conventies volgt voor routingbestanden die het framework moet interpreteren, terwijl je de overige code indeelt op basis van de aard van het project. Voor een nieuw project is een begrijpelijke aanpak doorgaans om src/app het middelpunt te maken voor URL's en pagina-entrypoints, UI die op meerdere plaatsen wordt gebruikt onder te brengen in components, featurespecifieke logica waar nodig in features te plaatsen en gedeelde hulpprogramma's in lib te bewaren. De App Router is de primaire routingaanpak in de huidige Next.js-versies, terwijl de Pages Router ondersteund blijft. nextjs.orgnextjs.org
Wanneer je voor het eerst een structuur vastlegt, is het niet belangrijk om zo veel mogelijk mappen te maken. Maak eerst onderscheid tussen bestanden die URL's creëren, bestanden die layouts, foutschermen en API's afhandelen, en code die alleen binnen een specifiek scherm wordt gebruikt. Dit artikel behandelt een onderhoudbare, op de App Router gerichte structuur op basis van die onderscheidingen.
Waarom bestaat er geen „enkele standaardstructuur” in Next.js?
Next.js biedt conventies om routes op te bouwen uit bestanden en mappen, maar schrijft niet exact voor welke mappen elk component en elk stuk bedrijfslogica moeten bevatten. Vooral in de App Router vertegenwoordigen mappen URL-segmenten, en speciale bestanden zoals page.tsx en route.ts creëren daadwerkelijke openbare entrypoints. Gewone bestanden die je binnen een route plaatst, worden daarentegen niet standaard externe routes. nextjs.org
Door deze eigenschap kunnen structuren zelfs tussen Next.js-applicaties verschillen. Een kleine marketingsite heeft misschien alleen app en een paar gedeelde componenten nodig. In een dienst met meerdere domeinen, zoals dashboards, bestellingen en accounts, maakt het scheiden van featurespecifieke code en gedeelde code het daarentegen gemakkelijker om de reikwijdte van wijzigingen te begrijpen. Dit is geen absolute regel die Next.js vereist, maar een aanpak voor codeorganisatie die een team boven op de routingconventies kiest.
Daarom is het minder verwarrend om „standaard” in de volgende twee lagen te zien.
| Categorie | Kenmerk | Typische voorbeelden |
|---|---|---|
| Conventies die Next.js interpreteert | Bestandsnamen en locaties hebben direct invloed op gedrag | app/page.tsx, app/layout.tsx, app/api/users/route.ts |
| Conventies die door het project zijn gedefinieerd | Het team bepaalt namen en grenzen voor eigen doeleinden | components, features, lib, hooks, types |
Het willekeurig wijzigen van de eerste laag kan routing of speciaal UI-gedrag veranderen. De tweede laag kun je weglaten of combineren, afhankelijk van de schaal en domeincomplexiteit. Een structuur zonder features is bijvoorbeeld mogelijk, en in een klein project hoef je niet elk stuk code overmatig op te splitsen.
Waarom moeten nieuwe projecten rond de App Router worden ontworpen?
De App Router is een aanpak voor het bouwen van pagina's en layouts op basis van de map app. De officiële documentatie raadt aan om van de Pages Router naar de App Router te migreren om de nieuwste React-functies te benutten, terwijl de Pages Router zelf ondersteund blijft. Daarom moet je de conventies van pages begrijpen wanneer je een bestaande projectstructuur onderhoudt of bestudeert, maar is het logisch om de App Router als standaardoptie te behandelen bij het ontwerpen van een nieuwe structuur. nextjs.org
De kern van de App Router is het verbinden van de URL-hiërarchie met de bestandshiërarchie. Zo wordt app/dashboard/page.tsx het entrypoint voor de pagina /dashboard, terwijl app/dashboard/layout.tsx eronder een layout kan bieden die wordt toegepast op subroutes van het dashboard. De hoofdmap-layout app/layout.tsx is de rootlayout die alle routes omhult. nextjs.org
Deze aanpak is goed geschikt om code op schermniveau dicht bij elkaar te houden. Tabs, tabellen en filter-UI die alleen nodig zijn voor /dashboard kunnen onder app/dashboard staan, terwijl knoppen of invoervelden die op meerdere schermen worden hergebruikt naar een externe gedeelde map kunnen worden verplaatst. Het belangrijkste criterium is niet „welke technologie is gebruikt om dit bestand te schrijven?”, maar „binnen welke reikwijdte wordt het hergebruikt?”
Het gebruik van de App Router betekent echter niet dat alle code in app moet staan. Je kunt app behandelen als een grens die URL's en speciale bestanden gemakkelijk leesbaar maakt, en gedeelde code of complexe feature-implementaties waar nodig in andere mappen onderbrengen. Next.js voert deze scheiding niet automatisch uit; het is een structurele conventie die het team consequent definieert.
Wanneer gebruik je een src-map, en wat blijft er in de hoofdmap staan?
src is optioneel. Als je het gebruikt, kun je app en de broncode van de applicatie onder src verzamelen, waardoor configuratiebestanden visueel van runtimecode worden gescheiden. public, package.json, next.config.js, tsconfig.json en .env.*-bestanden horen daarentegen in de projecthoofdmap. nextjs.org
Hieronder staat een begrijpelijk voorbeeld voor het gebruik van de App Router samen met src.
my-app/
├─ public/
│ ├─ images/
│ └─ fonts/
├─ src/
│ ├─ app/
│ │ ├─ layout.tsx
│ │ ├─ page.tsx
│ │ ├─ globals.css
│ │ ├─ (marketing)/
│ │ │ └─ about/
│ │ │ └─ page.tsx
│ │ ├─ dashboard/
│ │ │ ├─ layout.tsx
│ │ │ ├─ page.tsx
│ │ │ ├─ loading.tsx
│ │ │ ├─ error.tsx
│ │ │ └─ _components/
│ │ └─ api/
│ │ └─ users/
│ │ └─ route.ts
│ ├─ components/
│ ├─ features/
│ ├─ lib/
│ ├─ hooks/
│ └─ types/
├─ .env.local
├─ next.config.js
├─ package.json
└─ tsconfig.json
In dit voorbeeld is src alleen een grens voor applicatiecode, niet een verplicht mechanisme dat gedrag verandert. Als een bestaand project al app in de hoofdmap heeft, kan het beter zijn om de bestaande conventie consequent te volgen dan de toevoeging van src te forceren. Vooral wanneer mappen met de naam app of pages zowel in de hoofdmap als in src bestaan, krijgt de hoofdmap voorrang. Het is daarom belangrijk om tijdens een migratie niet langdurig dubbele structuren te laten bestaan. nextjs.org
Het praktische criterium om voor src te kiezen is eenvoudig. Het is nuttig als je configuratiebestanden duidelijk van productcode wilt scheiden of verwacht dat het aantal bronbestanden zal groeien. Omgekeerd hoef je het niet te gebruiken in een leerproject of een zeer klein project waarvan de hoofdmapstructuur al duidelijk is.
Welke bestanden in de app-map creëren daadwerkelijke routes?
In de App Router vertegenwoordigen mappen delen van een URL, oftewel segmenten. Alleen het creëren van een map app/dashboard maakt /dashboard echter nog geen openbare pagina. Als die map page.tsx bevat, wordt het een route die pagina-UI levert; als deze route.ts bevat, wordt het een API-endpoint op basis van een Route Handler. nextjs.org
Bekijk bijvoorbeeld de volgende indeling.
src/app/
├─ page.tsx
├─ about/
│ └─ page.tsx
├─ dashboard/
│ ├─ page.tsx
│ └─ reports/
│ └─ page.tsx
└─ api/
└─ users/
└─ route.ts
In dit geval komt page.tsx respectievelijk overeen met /, /about, /dashboard en /dashboard/reports. api/users/route.ts is geen UI-pagina; het definieert een API-endpoint. Zodra je begrijpt dat page.tsx en route.ts als openbare entrypoints van een route fungeren, wordt duidelijk waarom andere bestanden in dezelfde map kunnen staan. nextjs.org
Deze eigenschap kun je zien als colocatie. Colocatie is een organisatorische aanpak waarbij gerelateerde code dicht bij elkaar blijft. Tabelcomponenten die alleen in dashboard worden gebruikt, opmaakfuncties die specifiek zijn voor een scherm en code voor het transformeren van testgegevens kunnen bijvoorbeeld dicht bij app/dashboard worden geplaatst. Dit betekent niet dat je nooit gedeelde mappen moet gebruiken. Je hoeft alleen code die door andere routes wordt hergebruikt, te verplaatsen naar mappen met een bredere reikwijdte.
Hoe moeten layout-, loading- en error-bestanden worden gescheiden?
layout.tsx verwerkt de gedeelde UI-schil. De rootlayout app/layout.tsx omhult alle routes, terwijl een layout.tsx in een onderliggende map genest wordt toegepast op de subroutes daarvan. Als dashboardnavigatie bijvoorbeeld wordt gedeeld door /dashboard en /dashboard/reports, kan die in app/dashboard/layout.tsx staan. nextjs.org
page.tsx is de pagina-UI die op een specifiek pad wordt getoond. Als een layout de herhalende buitenstructuur is, ligt een pagina dichter bij de padspecifieke inhoud die daarin verandert. Door ze te scheiden hoef je gedeelde navigatie of kaders niet op elke pagina te herhalen.
De App Router biedt ook gereserveerde bestanden voor UI die specifiek is voor een bepaalde status. loading.tsx wordt gebruikt voor laad-UI, error.tsx voor fout-UI en not-found.tsx voor UI voor niet-gevonden pagina's. Anders dan gewone componentbestanden interpreteert Next.js deze bestanden voor specifieke rollen, dus plaats ze met hun rol en reikwijdte in gedachten. nextjs.org
Als je bijvoorbeeld een apart scherm wilt tonen terwijl gegevens onder /dashboard worden voorbereid, kun je app/dashboard/loading.tsx overwegen. Heb je binnen die reikwijdte een scherm voor foutherstel nodig, dan kun je app/dashboard/error.tsx overwegen. Je hoeft deze bestanden niet mechanisch aan elke map toe te voegen. Beslissen op basis van de vraag of elke route werkelijk afzonderlijke UI nodig heeft voor wachten, fouten of niet-gevonden statussen houdt de structuur beknopt.
Hoe worden dynamische routes en API-routes in mapnamen weergegeven?
Gebruik blokhaken wanneer een deel van een route niet vooraf vastligt. [slug] betekent één dynamisch segment, [...slug] betekent alle volgende subsegmenten en [[...slug]] betekent een optionele vorm waarbij die subsegmenten mogen ontbreken. nextjs.org
Een pagina waarvan de postidentifier verandert, kan bijvoorbeeld als volgt worden gestructureerd.
src/app/posts/
└─ [slug]/
└─ page.tsx
In deze structuur is [slug] geen vaste mapnaam; het is een placeholder die het veranderende deel van de URL ontvangt. Wanneer daarentegen meerdere niveaus van een route door één conventie moeten worden afgehandeld, kun je [...slug] of [[...slug]] overwegen. Welke notatie je kiest, hangt af van de vraag of er minimaal één subpad moet bestaan en of het geval zonder pad ook door hetzelfde scherm moet worden afgehandeld. nextjs.org
Bij het bouwen van API's is route.ts het entrypoint voor een Route Handler. Je kunt de URL-structuur dus weergeven met mappen als app/api/users/route.ts en route.ts aan het eind plaatsen. Voor zowel pagina's als API's kun je uit de maphiërarchie het globale pad afleiden. Het is echter beter om specifieke implementatiebestanden passend te scheiden, zodat UI en server-side verwerkingscode niet te veel door elkaar raken en lang worden binnen hetzelfde gebied. nextjs.org
Waarom zijn routegroepen en privémappen nodig?
Een map tussen haakjes, zoals (marketing), is een routegroep. Dit is een logische groepering die niet in de URL wordt opgenomen. Je kunt bijvoorbeeld marketinggerichte pagina's zoals bedrijfsinformatie en prijzen groeperen, terwijl je schermen voor productgebruik in een afzonderlijke structuur beheert. app/(marketing)/about/page.tsx wordt de route /about, zonder de groepsnaam. nextjs.org
Routegroepen zijn nuttig wanneer je layoutgrenzen of code-eigenaarschap wilt tonen zonder de URL te wijzigen. Omdat de groepsnaam niet in de URL verschijnt, ontstaat er echter een conflict als afzonderlijke groepen uiteindelijk dezelfde URL creëren. Daarnaast kan een configuratie die tussen meerdere rootlayouts beweegt een volledige paginalading veroorzaken. Het opsplitsen van rootlayouts moet je daarom niet zomaar toepassen enkel om mappen er netjes uit te laten zien. nextjs.org
Mappen die met een underscore beginnen, zoals _components en _lib, zijn privémappen en worden uitgesloten van routing. In de App Router worden gewone bestanden niet standaard routes, dus een underscore is niet strikt noodzakelijk. Toch kan dit nuttig zijn als je de grens tussen speciale routingbestanden en interne implementatie zichtbaar wilt markeren, of verwarring met gereserveerde bestandsnamen wilt voorkomen. nextjs.org
app/dashboard/_components/summary-card.tsx maakt bijvoorbeeld duidelijk dat het dashboard-specifieke UI is. Als dat component echter herhaaldelijk in andere features wordt gebruikt, is het natuurlijker om te overwegen het uit de underscoremap te verplaatsen naar gedeelde components of een passende featuregrens. Onthoud dat een mapprefix geen toegangscontrolemechanisme is; het is notatie die de rol van de code communiceert.
Hoe onderscheid je components, features, lib, hooks en types?
Deze mappen zijn optionele organisatorische keuzes, geen gereserveerde conventies van de App Router. Daarom zijn de verantwoordelijkheidsgrenzen waarover het team overeenstemming bereikt belangrijker dan de namen zelf. De volgende onderscheidingen vormen een veelvoorkomend beginpunt.
| Map | Code die daar doorgaans staat | Plaatsingscriterium |
|---|---|---|
components | UI die over meerdere schermen wordt hergebruikt | Is het niet gebonden aan een specifieke URL of domein? |
features | Implementatie op feature- of domeinniveau | Is er een duidelijk bedrijfsconcept, zoals accounts, bestellingen of dashboards? |
lib | Gedeelde hulpprogramma's en clients | Is het een gedeeld hulpmiddel in plaats van UI? |
hooks | Herbruikbare hooks | Delen meerdere componenten dezelfde status of hetzelfde gedrag? |
types | Gedeelde typen | Verwijzen meerdere gebieden naar dezelfde typedefinities? |
components kan niet alleen generieke knoppen bevatten, maar ook samengestelde UI die door meerdere features wordt gedeeld. Elk UI-element vanaf het begin een wereldwijd gedeeld component maken, kan echter implementaties abstraheren die in werkelijkheid slechts door één scherm nodig zijn. Een andere aanpak is om het aanvankelijk dicht bij de route te houden en het pas te verplaatsen wanneer het stabiel op twee of meer plaatsen wordt gebruikt en een duidelijke gedeelde interface heeft.
features is vooral goed leesbaar wanneer een domeingerichte structuur nodig is. Als bestellingen en accounts bijvoorbeeld elk onafhankelijke schermen, UI en gegevensverwerkingscode hebben, kun je ze groeperen als features/orders en features/account. Het maken van te veel featuremappen in een eenvoudige site kan mensen daarentegen dwingen om door veel mappen te navigeren alleen om bestanden te vinden. Je kunt ze beter pas invoeren wanneer featuregrenzen aansluiten bij echte productconcepten.
lib is een geschikte locatie voor fundamentele niet-UI-code, zoals gedeelde hulpprogramma's of serverclients. Als elk functiebestand zich echter in lib opstapelt, kan dit een grote, moeilijk te begrijpen opslagplaats worden. Een praktische regel is om hulpmiddelen die slechts door één feature worden gebruikt dicht bij die feature of route te houden, en alleen code die op meerdere plaatsen wordt gedeeld naar lib te verplaatsen.
Waarom horen public en bestanden met omgevingsvariabelen in de projecthoofdmap?
public is de map in de projecthoofdmap voor statische bestanden. Bestanden die je daar plaatst, worden vanaf het rootpad aangeboden; naar public/profile.png verwijs je bijvoorbeeld als /profile.png. Dit biedt één plek voor statisch aangeboden bestanden, zoals afbeeldingen en lettertypen. nextjs.org
Ook als je src gebruikt, moet je de structuur niet zien als het verplaatsen van public naar src/public. public blijft in de projecthoofdmap, en ook package.json, next.config.js, tsconfig.json en .env.* worden vanuit de hoofdmap beheerd. Vooral omdat lokale bestanden met omgevingsvariabelen zoals .env.local geheimen kunnen bevatten, is het belangrijk om een werkwijze vast te leggen waarin ze niet in versiebeheer worden opgenomen. nextjs.org
Door de rollen van statische assets en applicatiebroncode te onderscheiden, worden paden gemakkelijker te interpreteren. Bestanden in src/app zijn code die schermen of routing opbouwt, terwijl bestanden in public statische assets zijn waarnaar via een URL wordt verwezen. Zelfs wanneer dezelfde afbeelding op een scherm wordt gebruikt, moet je de bezorgmethode en het referentiepad anders begrijpen, afhankelijk van waar het bestand is geplaatst.
Hoe verschilt dit van de structuur van de Pages Router?
De Pages Router werkt door bestanden in de directory pages als routes te behandelen. Zo komt pages/index.tsx overeen met / en pages/about.tsx met /about. Gereserveerde bestandsconventies wijzen ook speciale rollen toe aan _app, _document, 404, 500 en andere bestanden. nextjs.org
Het is gemakkelijk om fouten te maken als je de App Router en Pages Router ziet als benaderingen die alleen verschillen in mapnaam. In de App Router verdelen speciale bestanden zoals page.tsx, layout.tsx en route.ts onder mapsegmenten de verantwoordelijkheden, terwijl gewone bestanden standaard geen routes zijn. In de Pages Router zijn bestanden in pages directer gekoppeld aan routes. nextjs.orgnextjs.org
Wanneer je met een bestaand Pages Router-project werkt, moet je de huidige op pages gebaseerde conventies respecteren. Wanneer je daarentegen een nieuw project start, verlaagt het de overhead om de structuur te baseren op een voorbeeld dat op de App Router is gericht en organisatorische mappen alleen toe te voegen wanneer dat echt nodig is. Het is belangrijk om de conventies van beide routers niet binnen hetzelfde directoryontwerp te verwarren.
Welke criteria moet je gebruiken om een structuur voor een echt project te kiezen?
Begin eerst met de URL-structuur. Maak een lijst van de belangrijkste paden die gebruikers zullen openen en bepaal vervolgens welke gedeelde layouts elk pad gebruikt. Geef het resultaat weer via mappen in app en de plaatsing van page.tsx en layout.tsx. Gebruik routegroepen wanneer er een duidelijke reden is om schermgebieden of layouts op te delen zonder URL's te wijzigen. nextjs.orgnextjs.org
Beoordeel ten tweede de reikwijdte van hergebruik. Houd code die slechts door één route wordt gebruikt dicht bij die route. UI die door meerdere routes wordt gebruikt, kan naar een bredere reikwijdte zoals components worden verplaatst, en hulpprogramma's die door meerdere features worden gebruikt kunnen naar lib. In plaats van alles vanaf het begin te generaliseren, helpt het om code pas te scheiden wanneer werkelijke patronen van hergebruik en wijzigingen ontstaan. Zo verminder je onnodige abstractie.
Houd ten derde rekening met feature-onafhankelijkheid. Als features met duidelijke verantwoordelijkheidsgebieden en terminologie, zoals accounts, beheer of bestellingen, groter worden, kunnen domeingrenzen zoals features nuttig zijn. Wanneer er daarentegen weinig schermen zijn en de verschillen tussen features gering zijn, kan colocatie in app met een klein aantal gedeelde mappen voldoende zijn.
Controleer ten vierde de zoekkosten voor het team. Wanneer een nieuw teamlid code voor een specifieke URL zoekt, moet diegene het app-pad kunnen volgen en de pagina en specifieke implementatie kunnen vinden. De namen en locaties van gedeelde knoppen of hulpprogramma's moeten eveneens voorspelbaar zijn. Een goede structuur komt meer voort uit deze voorspelbaarheid dan uit modieuze mapnamen.
Welke misvattingen en structurele valkuilen moet je vermijden?
De eerste misvatting is dat „het maken van een map meteen een URL creëert”. In de App Router vertegenwoordigt een map een segment, maar heeft deze page.tsx of route.ts nodig om een openbare pagina of API-endpoint te worden. Deze regel maakt het mogelijk dat gerelateerde interne bestanden samen in de routemap staan. nextjs.org
De tweede misvatting is dat „zonder een underscoremap alle interne bestanden worden blootgesteld”. Gewone bestanden in de App Router zijn standaard geen routes. Een naam zoals _components is geen vereiste beveiligingsfunctie; het is een organisatorisch hulpmiddel dat interne implementatie aanduidt en de map uitsluit van routing. nextjs.org
De derde misvatting is dat „namen van routegroepen ook in URL's worden opgenomen”. De naam tussen haakjes in (marketing) wordt uitgesloten van de URL. Vanwege dit gemak moet je ervoor zorgen dat verschillende groepen niet dezelfde uiteindelijke URL creëren. Als je meerdere rootlayouts splitst, is de mogelijkheid van een volledige paginalading bij navigatie tussen groepen een ander aandachtspunt voordat je de structuur ontwerpt. nextjs.org
Vermijd ten slotte de fout om tijdens de introductie van src gelijknamige app- of pages-directories zowel in de hoofdmap als in src te behouden. Omdat de hoofdmap in dit geval voorrang krijgt, kan het lijken alsof de broncode die je verwachtte niet wordt uitgevoerd. Een structuur wijzigen is niet slechts het in één keer toevoegen van mappen; ga stapsgewijs te werk en controleer welke directory daadwerkelijk als basis voor routing fungeert. nextjs.org
Conclusie: de standaard draait om rolgrenzen, niet om een mappenlijst
Het vertrekpunt voor een Next.js-projectstructuur zijn de routingconventies die worden gedefinieerd door app en speciale bestanden. In een nieuw App Router-project kun je src/app het middelpunt maken voor URL's, pagina's en layouts; public als map voor statische assets in de hoofdmap houden; en components, features, lib, hooks en types kiezen volgens de werkelijke reikwijdte van codehergebruik en domeincomplexiteit. nextjs.orgnextjs.org
Uiteindelijk is een goede structuur niet de structuur met de meeste mappen. Het is een structuur waarin teamleden gemakkelijk kunnen voorspellen waar een scherm voor een specifieke route staat, waar code voor dat scherm staat en waar code staat die op meerdere plaatsen wordt gedeeld. Volg de bestandsconventies van Next.js nauwkeurig en pas vervolgens de organisatorische aanpak daarboven stapsgewijs aan naarmate de groeisnelheid en wijzigingspatronen van het project evolueren.