Комментарии 2
Есть два места, на которых эта схема обычно и ломается, и в статье их нет. Первое: notFound() из intercepted-слота. Если событие удалили, а карточку открыли кликом из списка, в ряде версий Next это поднимается до not-found границы всего маршрута — список вместе с модалкой исчезает, хотя прямой заход на тот же URL отдаёт честную 404-страницу. Обычно это лечат своим состоянием «недоступно» внутри модалки, а notFound() оставляют только отдельной странице. Второе: блокировка фона. Если ради iOS вешать position: fixed на body, после закрытия список прыгает к началу, потому что scrollY надо сохранять руками; scroll={false} не выручает — он про навигацию, а не про позицию. Как у вас сделаны эти два места?
Да, эти два случая у меня разведены.
В intercepted route notFound() не вызывается. Если fetchEventById() не вернул событие, @modal/(.)events/[id] остаётся в modal slot и рендерит Event preview unavailable с возможностью закрыть модалку. notFound() вызывается только в обычном events/[id]/page.tsx, поэтому прямой заход на отсутствующий event получает 404.
С блокировкой фона здесь другой вариант. position: fixed стоит на .modal-backdrop, сам body не переводится в fixed и scrollY вручную не сохраняется. Поэтому после закрытия восстанавливать позицию из-за body lock не требуется. scroll={false} используется только для навигации и само по себе проблему с body { position: fixed } действительно не решало бы.
Если добавлять отдельный iOS scroll lock через фиксацию body, тогда да, там уже нужно сохранять текущий scrollY перед открытием и восстанавливать его после закрытия.

URL на два режима, modal поверх списка и SEO detail page в Next.js App Router