The routing rule
One rule covers almost everything:
Each folder under
app/is one URL segment, and thepage.jsinside it is the UI for that URL.
So:
app/page.js→/app/about/page.js→/aboutapp/blog/drafts/page.js→/blog/drafts
Nest folders and you nest URL segments, exactly like directories on disk. Compare this with Backend with Node.js, where you wrote app.get("/blog/drafts", handler) by hand. Next.js derives the same route table from your file system, which means the route list can never drift out of sync with the code.
Planning a bakery site from its URL list
This mapping is how working engineers plan a site: write the URL list first, and the folders fall out mechanically. Say a bakery wants four pages.
| URL the customer types | File you create |
|---|---|
/ | app/page.js |
/menu | app/menu/page.js |
/menu/cakes | app/menu/cakes/page.js |
/contact | app/contact/page.js |
Two traps in that table
/menuand/menu/cakesare separate pages. Nesting thecakesfolder insidemenudoes not remove the need formenu/page.jsif/menuitself should render something.- Folder names become public URL segments, so name them the way users should read them:
menu, notMenuStuff.
Building the URL /shop/cart
That URL needs two folders and one page file: app/shop/cart/page.js.
The two folders, shop then cart, supply the two URL segments. The page.js inside the deepest folder supplies the UI rendered there. A file named cart.js sitting inside app/shop/ would not work, because it is just an ordinary module as far as the router is concerned, not a route.
routeFromPath: the mapping expressed as code
The folder-to-URL rule is mechanical enough to express as a plain JavaScript function, which is a good way to make it concrete. A function routeFromPath(filePath) receives a path like "app/blog/drafts/page.js", strips the leading "app" and the trailing "/page.js", and returns the URL. The homepage "app/page.js" is the one special case: after stripping both ends nothing is left, so it returns "/".
function routeFromPath(filePath) { let route = filePath.slice("app".length, filePath.length - "/page.js".length); return route === "" ? "/" : route; } console.log(routeFromPath("app/page.js")); console.log(routeFromPath("app/about/page.js")); console.log(routeFromPath("app/blog/drafts/page.js"));
Output
/ /about /blog/drafts
How the slicing works
slice(start, end)cuts from both ends in one call: start after"app"(3 characters) and end before"/page.js"(8 characters).- After slicing,
"app/page.js"leaves an empty string, so the ternary returns"/"for the homepage. - Nothing here is Next.js code. The framework does this same derivation internally, by reading your file tree at build time instead of parsing strings.
Adding a seasonal page deeper in the tree
The bakery wants a page at /menu/cakes/holiday. The file that makes it render is app/menu/cakes/holiday/page.js.
Each URL segment is one folder, so the URL needs the folder chain menu → cakes → holiday, and the UI for the URL is always the page.js inside the deepest folder. Putting a file named holiday.js inside app/menu/cakes/ would not create the route, because the router treats it as ordinary importable code.