Express 24,430 gives you a router you program: you call app.get('/users/:id', handler) and the order of your calls decides which handler wins. The App Router works the other way round. You do not register routes at all. You create folders under app/, drop reserved file names into them, and Next.js 10,514 derives the routing table from the directory tree at build time. The path on disk is the statement.
That trade buys three things Express cannot give you for free. Every URL segment gets its own place to hang shared UI, so a layout for /dashboard is written once and wraps everything below it. Every segment is also a natural boundary for loading and error states, which is what makes streaming practical (Navigation and Loading). And because the tree is static, Next.js decides at build time which URLs it can prerender.
The cost is vocabulary. Nine file names and six folder naming conventions carry all the meaning, and a misplaced underscore or bracket silently changes which URLs exist. This section walks that vocabulary with one working application, built with Next.js 16.3.5 and React 19.3.0 7,897 , whose routes appear in every build listing that follows.
One change deserves flagging up front, because material written before Next.js 15 gets it wrong: params is a promise. Any page, layout, route.js handler or generateMetadata that reads a dynamic segment must await it, or read it with React's use in a Client Component.