Summary
The App Router guide recommends, for the app-layer conflict:
The solution is to move the Next.js app folder to the project root and import FSD pages from src ... into the Next.js app folder.
You will also need to add a pages folder to the project root, otherwise Next.js will try to use src/pages as the Pages Router even if you use the App Router, which will break the build.
This works for building, but the empty root pages/ folder has a non-obvious side effect: it changes the type signatures of the next/navigation hooks across the whole app, even though the project only uses the App Router.
This is not just "a type error in my code" — the public API signature itself changes. I think the guide should at least document this trade-off.
Why it happens
When a project contains both an app and a pages directory, Next.js switches the next/navigation hooks to their Pages-Router compatibility signatures, which add | null. This is what the bundled type augmentation in next does — next/navigation-types/compat/navigation.d.ts (Next.js 16.2.6):
declare module 'next/navigation' {
export function useSearchParams(): ReadonlyURLSearchParams | null
export function usePathname(): string | null
export function useParams<
T extends Record<string, string | string[]> = Record<string, string | string[]>,
>(): T | null
export function useSelectedLayoutSegments(): string[] | null
export function useSelectedLayoutSegment(): string | null
}
So following the FSD guide (adding an empty pages/ purely to satisfy the Pages-Router probe) opts an App-Router-only project into Pages-Router migration-compat types it does not want — for all five hooks above.
The official docs describe this for useSearchParams:
If an application includes the /pages directory, useSearchParams will return ReadonlyURLSearchParams | null. The null value is for compatibility during migration since search params cannot be known during prerendering of a page that doesn't use getServerSideProps
— https://nextjs.org/docs/app/api-reference/functions/use-search-params#returns
For reference, usePathname documents the same automatic adjustment ("if your project contains both an app and a pages directory, Next.js will automatically adjust the return type of usePathname"):
Impact
useSearchParams, usePathname, useParams, useSelectedLayoutSegments, useSelectedLayoutSegment all become nullable.
- Under
strict TS, existing call sites like useSearchParams().toString() or .get(...) now error (TS18047: ... is possibly 'null'), forcing either null guards or non-null assertions throughout the codebase — purely as a consequence of a directory that exists only to silence a build probe.
- This becomes a barrier specifically for developers who already use the App Router and want to adopt FSD: their project has no
pages/ directory today, so the next/navigation hooks are non-nullable. The moment they follow this guide and add the empty pages/ folder, every existing call site is pushed onto the | null compat signatures and starts failing type-check — purely as a consequence of adopting FSD, not because of any real Pages-Router usage.
Suggestion
At minimum, the App Router guide should document this signature side effect so it is not a surprise.
It would also help to discuss alternatives for App-Router-only projects that want to avoid the compat nullability:
-
Rename just the FSD pages layer (e.g. to views), so no pages/ directory exists at all.
-
Prefix every FSD layer with _ (_pages, _entities, _features, _widgets, _shared, ...) and keep app as the Next.js one. This keeps the original FSD layer names readable, gives a consistent "_ = FSD layer" rule, and lets the Next.js app stay under src/app without moving it out of src:
src/
├── app/ # Next.js App Router (routing only)
├── _app/ # FSD app layer (providers / global config)
├── _pages/ # FSD pages layer
├── _widgets/ # FSD widgets layer
├── _features/ # FSD features layer
├── _entities/ # FSD entities layer
└── _shared/ # FSD shared layer
(There is no separate empty pages/ folder anywhere, and the only directory named app is the Next.js one.)
Both avoid adding an empty root pages/ folder, so the next/navigation hooks keep their non-null App Router signatures. These are just ideas, though — I'd be interested in what approach the maintainers would recommend here.
Summary
The App Router guide recommends, for the
app-layer conflict:This works for building, but the empty root
pages/folder has a non-obvious side effect: it changes the type signatures of thenext/navigationhooks across the whole app, even though the project only uses the App Router.This is not just "a type error in my code" — the public API signature itself changes. I think the guide should at least document this trade-off.
Why it happens
When a project contains both an
appand apagesdirectory, Next.js switches thenext/navigationhooks to their Pages-Router compatibility signatures, which add| null. This is what the bundled type augmentation innextdoes —next/navigation-types/compat/navigation.d.ts(Next.js 16.2.6):So following the FSD guide (adding an empty
pages/purely to satisfy the Pages-Router probe) opts an App-Router-only project into Pages-Router migration-compat types it does not want — for all five hooks above.The official docs describe this for
useSearchParams:— https://nextjs.org/docs/app/api-reference/functions/use-search-params#returns
For reference,
usePathnamedocuments the same automatic adjustment ("if your project contains both anappand apagesdirectory, Next.js will automatically adjust the return type ofusePathname"):Impact
useSearchParams,usePathname,useParams,useSelectedLayoutSegments,useSelectedLayoutSegmentall become nullable.strictTS, existing call sites likeuseSearchParams().toString()or.get(...)now error (TS18047: ... is possibly 'null'), forcing either null guards or non-null assertions throughout the codebase — purely as a consequence of a directory that exists only to silence a build probe.pages/directory today, so thenext/navigationhooks are non-nullable. The moment they follow this guide and add the emptypages/folder, every existing call site is pushed onto the| nullcompat signatures and starts failing type-check — purely as a consequence of adopting FSD, not because of any real Pages-Router usage.Suggestion
At minimum, the App Router guide should document this signature side effect so it is not a surprise.
It would also help to discuss alternatives for App-Router-only projects that want to avoid the compat nullability:
Rename just the FSD
pageslayer (e.g. toviews), so nopages/directory exists at all.Prefix every FSD layer with
_(_pages,_entities,_features,_widgets,_shared, ...) and keepappas the Next.js one. This keeps the original FSD layer names readable, gives a consistent "_= FSD layer" rule, and lets the Next.jsappstay undersrc/appwithout moving it out ofsrc:(There is no separate empty
pages/folder anywhere, and the only directory namedappis the Next.js one.)Both avoid adding an empty root
pages/folder, so thenext/navigationhooks keep their non-null App Router signatures. These are just ideas, though — I'd be interested in what approach the maintainers would recommend here.