The app/ directory is not a folder rename with layouts.
Next 13 landed two weeks ago and shipped it in beta. app/ inverts the data model of your application, and porting pages/ file by file is the fastest way to hate it.
The inversion
Since getInitialProps, and later getServerSideProps, the Next model was fixed: pages fetch, components receive. Data enters at the route's top and props waterfall down. Everything below is a client component, because everything is a client component.
The app/ directory flips both halves. Every component is a React Server Component by default, and any component can fetch, because components can be async now. That RFC opened in December 2020, and it merged the day Next 13 shipped. That tells you who the launch vehicle is.
// app/papers/page.jsx
export default async function Papers() {
const papers = await getPapers()
return <PaperList papers={papers} />
}The fetch options replace the old API surface one-to-one, per the launch post:
fetch(url, { cache: 'force-cache' }) // getStaticProps
fetch(url, { cache: 'no-store' }) // getServerSideProps
fetch(url, { next: { revalidate: 10 } }) // ISRDedup is real and subtle. React patches fetch in the server runtime, so two components requesting the same URL in one render pass cost one request. The dedup lives in React. Next inherits it.
The wire format is right there
None of this is magic. View source on any app/ page. The RSC render travels as the React Flight format, inlined into script chunks.
<script>self.__next_f.push([1,"J0:[\"$\",\"main\",null,{\"children\":…"])</script>Rendered elements are ["$","div",null,{…}] tuples. Client components appear as module references. The bundler manifest resolves them, and react-server-dom-webpack is the serializer.
On soft navigation the same stream comes over the network, and the router caches it per route segment. The Layouts RFC from May spells out that navigation fetches "special instructions (not HTML or JSON)" and re-renders only the changed segment. That is the mechanism behind layouts that survive navigation, after six years of getLayout hacks.
The boundary
Client components still exist. They are opt-in, and they are the leaves.
'use client'The directive is three weeks old as a convention. Module Conventions v2 killed the .server.js and .client.js filename scheme from the earlier RFCs. Markbåge landed it on October 11. His model in one quote: "everything is implicitly shared until proven otherwise, and then the build errors".
'use client' does not mean "this runs on the client". The directive is a module-graph marker. Everything imported below it ships to the browser, and every prop crossing it must survive Flight serialization. Functions do not cross. Class instances do not cross. Your Date arrives as a string.
Two consequences follow.
- One
useStatethree levels down client-taints its whole import subtree. You will spend the first week discovering which components secretly do. - The escape hatch is compositional. A Client Component can receive a Server Component as
children, so interactive shells wrap server-rendered content. The Layouts RFC documents this children-slot pattern. It is the best trick for keeping bundles lean.
Mount one leaf route
Do not migrate. The beta is honest about being a beta, and the cache semantics are still settling. Mount one leaf route in app/, because both directories coexist. Feel where the boundary wants to sit. Watch how much of your bundle evaporates when list rendering stays on the server. Then skim the rest of the Layouts RFC: route groups, @slot parallel routes and (..) intercepting routes are all specced there, waiting.
Treat it as a new framework that happens to share a router.