I am having a problem with intercepting routes and especially modals to work in Next.js 16 its latest version. I mean, everything works just fine in production, but only if I disable the “cacheComponents” feature, and this is how it works now:
Once you go to the “Notes” section and sign in as a demo user, either by clicking the “Notes” icon in the nav bar or by directly going to:
You will be presented with a bunch of example notes where you can click on any note to view its details, add a new note, or edit the existing note. The whole interface depends heavily on modals/intercepting routes so that the user always sees their collection of notes. As you can see, it works just fine—the modals are being presented as advertised. Once I enable the “cacheComponents” feature, it still works fine, but only on my local machine. Things change when I deploy to Vercel.
Now, only one intercepting route opens a modal correctly “New Note” at:
app/@notes/(.)notes/new/page.tsx
here is the code:
// react
import { Suspense } from "react";
// components
import NoteModal from "@/features/notes/components/note-modal";
import BrowseBar from "@/features/notes/components/browse-bar";
import NewNoteForm from "@/features/notes/components/NewNoteForm";
// assets
import { DocumentPlusIcon } from "@heroicons/react/24/outline";
// Page remains the fast, static shell
export default function Page() {
return (
<Suspense fallback={<PageSkeleton />}>
<PageContent />
</Suspense>
);
}
// This new async component contains the dynamic logic
async function PageContent() {
return (
<NoteModal icon={<DocumentPlusIcon className="size-11 flex-none" />} browseBar={<BrowseBar kind="note-new" />}>
<NewNoteForm inNoteModal />
</NoteModal>
);
}
function PageSkeleton() {
return null;
}
Any other intercepting route fails with this message for example in the browser’s console window:
848cf2c4b414a902.js:1
GET https://total-recall-ai-git-cc-remis-projects-738a757c.vercel.app/notes/8f149149-f54c-4255-9824-a7e68073ed46?_rsc=1vq60 404 (Not Found)
Here is one of the problematic intercepting routes:
app/@notes/(.)notes/[id]/page.tsx
here is the code:
// react
import { Suspense } from "react";
// services, features, and other libraries
import { validatePageInputs } from "@/lib/helpers";
import { NoteDetailsPageSchema } from "@/features/notes/schemas/noteDetailsPage";
// components
import NoteModal from "@/features/notes/components/note-modal";
import BrowseBar from "@/features/notes/components/browse-bar";
import NoteDetails from "@/features/notes/components/NoteDetails";
// assets
import { DocumentIcon } from "@heroicons/react/24/outline";
// Page remains the fast, static shell
export default function Page({ params, searchParams }: PageProps<"/notes/[id]">) {
return (
<Suspense fallback={<PageSkeleton />}>
<PageContent params={params} searchParams={searchParams} />
</Suspense>
);
}
// This new async component contains the dynamic logic
async function PageContent({ params, searchParams }: PageProps<"/notes/[id]">) {
// Safely validate next.js route inputs (`params` and `searchParams`) against a zod schema; return typed data or trigger a 404 on failure
const {
params: { id: noteId },
} = await validatePageInputs(NoteDetailsPageSchema, { params, searchParams });
return (
<NoteModal icon={<DocumentIcon className="size-11 flex-none" />} browseBar={<BrowseBar kind="note-details" />} noteId={noteId}>
<NoteDetails noteId={noteId} />
</NoteModal>
);
}
function PageSkeleton() {
return null;
}
The only difference from the previous intercepting route is this line which “awaits” stuff:
// Safely validate next.js route inputs (`params` and `searchParams`) against a zod schema; return typed data or trigger a 404 on failure
const {
params: { id: noteId },
} = await validatePageInputs(NoteDetailsPageSchema, { params, searchParams });
I noticed that even the simplest “await” breaks the whole intercepting route. By the way, you can access the whole code and see the entire file structure at:








