Dynamic intercepting parallel route is server side rendered
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 22.4.0: Mon Mar 6 21:00:41 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T8103
Binaries:
Node: 18.12.1
npm: 9.8.0
Yarn: 1.22.19
pnpm: 8.6.0
Relevant Packages:
next: 13.4.9
eslint-config-next: 13.4.10
react: 18.2.0
react-dom: 18.2.0
typescript: 5.1.6
Next.js Config:
/** @type {import('next').NextConfig} */
const nextConfig = {
distDir: 'build',
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
},
}
module.exports = nextConfig
warn - Latest canary version not detected, detected: "13.4.9", newest: "13.4.10".
Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
Read more - https://nextjs.org/docs/messages/opening-an-issue
(I use version 13.4.9, because in 13.4.10 intercepted parallel routes do not work at all)
Which area(s) of Next.js are affected? (leave empty if unsure)
App Router, Routing (next/router, next/navigation, next/link)
Link to the code that reproduces this issue or a replay of the bug
https://drive.google.com/file/d/1wCb9VkpbAlH1bT-iIcP3dTYnEEOT3Glw/view?usp=sharing
To Reproduce
/src/app/adspaces/[id]/page.tsx
import { AdspaceModalClient } from '@/app/@modal/(.)adspaces/[id]/page.client'
import { prisma } from '@/server/prismaDb'
import { type NextPage } from 'next'
import { notFound } from 'next/navigation'
export const generateStaticParams = async () => {
const adspaces = await prisma.adspace.findMany({
select: {
id: true,
},
})
return adspaces.map(adspace => ({
id: String(adspace.id),
}))
}
interface AdspacesProps {
params: {
id: string
}
}
const Adspaces: NextPage<AdspacesProps> = async ({ params }) => {
const { id } = params
const adspace = await prisma.adspace.findUnique({
where: {
id: Number(id),
},
include: {
sides: true,
},
})
if (!adspace) {
notFound()
}
return <AdspaceModalClient adspace={adspace} />
}
export default Adspaces
/src/app/@modal/(.)adspaces/[id]/page.tsx
import { AdspaceModalClient } from '@/app/@modal/(.)adspaces/[id]/page.client'
import { prisma } from '@/server/prismaDb'
import { type NextPage } from 'next'
import { notFound } from 'next/navigation'
export const generateStaticParams = async () => {
const adspaces = await prisma.adspace.findMany({
select: {
id: true,
},
})
return adspaces.map(adspace => ({
id: String(adspace.id),
}))
}
interface AdspacesModalProps {
params: {
id: string
}
}
const AdspacesModal: NextPage<AdspacesModalProps> = async ({ params }) => {
const { id } = params
const adspace = await prisma.adspace.findUnique({
where: {
id: Number(id),
},
include: {
sides: true,
},
})
if (!adspace) {
notFound()
}
return <AdspaceModalClient adspace={adspace} />
}
export default AdspacesModal
/src/app/layout.tsx
import { TailwindIndicator } from '@/components/tailwind-indicator'
import { Toaster } from '@/components/ui/toaster'
import { WebVitals } from '@/components/web-vitals'
import { websiteMetadata } from '@/config/metadata.config'
import '@/styles/global.css'
import { Metadata } from 'next'
import { Inter } from 'next/font/google'
export const metadata: Metadata = websiteMetadata
const font = Inter({
subsets: ['latin', 'cyrillic', 'cyrillic-ext'],
preload: false,
})
interface RootLayoutProps {
children: React.ReactNode
modal: React.ReactNode
}
const RootLayout: React.FC<RootLayoutProps> = props => {
return (
<html lang="en">
<body style={font.style}>
{props.children}
{props.modal}
{/* */}
<Toaster />
<TailwindIndicator />
<WebVitals />
</body>
</html>
)
}
export default RootLayout
Describe the Bug
/adspaces/[id] builds normally (SSG), but when I do the same in /(.)adspaces/[id], it somehow becomes SSR, even though each of these pages exports the same generateStaticParams function.
$ next build
- info Loaded env from /Users/oleh/Desktop/advertize-new/.env.production
- info Loaded env from /Users/oleh/Desktop/advertize-new/.env
- info Creating an optimized production build
- info Compiled successfully
- info Linting and checking validity of types
- info Collecting page data
[ ] - info Generating static pages (0/53)- warn Entire page /order deopted into client-side rendering. https://nextjs.org/docs/messages/deopted-into-client-rendering /order
- warn Entire page /(.)order deopted into client-side rendering. https://nextjs.org/docs/messages/deopted-into-client-rendering /(.)order
- info Generating static pages (53/53)
- info Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 13.8 kB 134 kB
├ λ /(.)adspaces/[id] 2.11 kB 117 kB
├ ○ /(.)order 1.32 kB 161 kB
├ ● /adspaces/[id] 2.11 kB 117 kB
├ ├ /adspaces/1
├ ├ /adspaces/2
├ ├ /adspaces/3
├ └ [+37 more paths]
├ λ /api/order 0 B 0 B
├ λ /api/revalidate 0 B 0 B
├ ○ /favicon.ico 0 B 0 B
├ ○ /manifest.webmanifest 0 B 0 B
├ ○ /opengraph-image.png 0 B 0 B
├ ○ /order 2.4 kB 167 kB
├ ○ /robots.txt 0 B 0 B
├ ○ /sitemap.xml 0 B 0 B
└ ○ /twitter-image.png 0 B 0 B
+ First Load JS shared by all 77.6 kB
├ chunks/698-fa1560bd3d687c07.js 25 kB
├ chunks/bce60fc1-1099bf5e527a55df.js 50.5 kB
├ chunks/main-app-5c31dc370cc81b80.js 212 B
└ chunks/webpack-3994cc1df7b93e85.js 1.81 kB
Route (pages) Size First Load JS
─ ○ /404 181 B 79.4 kB
+ First Load JS shared by all 79.2 kB
├ chunks/framework-8883d1e9be70c3da.js 45 kB
├ chunks/main-39568768d6412e27.js 32.2 kB
├ chunks/pages/_app-b75b9482ff6ea491.js 195 B
└ chunks/webpack-3994cc1df7b93e85.js 1.81 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
Expected Behavior
Expected /(.)adspaces/[id] page to be SSG as well. It's something like this:
$ next build
Route (app) Size First Load JS
┌ ○ / 13.8 kB 134 kB
├ ● /(.)adspaces/[id] 2.11 kB 117 kB
├ ├ /adspaces/1
├ ├ /adspaces/2
├ ├ /adspaces/3
├ └ [+37 more paths]
├ ○ /(.)order 1.32 kB 161 kB
├ ● /adspaces/[id] 2.11 kB 117 kB
├ ├ /adspaces/1
├ ├ /adspaces/2
├ ├ /adspaces/3
├ └ [+37 more paths]
├ λ /api/order 0 B 0 B
├ λ /api/revalidate 0 B 0 B
├ ○ /favicon.ico 0 B 0 B
├ ○ /manifest.webmanifest 0 B 0 B
├ ○ /opengraph-image.png 0 B 0 B
├ ○ /order 2.4 kB 167 kB
├ ○ /robots.txt 0 B 0 B
├ ○ /sitemap.xml 0 B 0 B
└ ○ /twitter-image.png 0 B 0 B
+ First Load JS shared by all 77.6 kB
├ chunks/698-fa1560bd3d687c07.js 25 kB
├ chunks/bce60fc1-1099bf5e527a55df.js 50.5 kB
├ chunks/main-app-5c31dc370cc81b80.js 212 B
└ chunks/webpack-3994cc1df7b93e85.js 1.81 kB
Route (pages) Size First Load JS
─ ○ /404 181 B 79.4 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
Which browser are you using? (if relevant)
Brave, Chrome
How are you deploying your application? (if relevant)
Firebase hosting + firebase functions (firebase does it on its own, I use experimental firebase frameworks)
+1 on this! @Oleshkooo Have you found the issue here?
Yep seems like generateStaticParams is not triggering for pages inside @slot
+1 on this! @Oleshkooo Have you found the issue here?
Accidentally closed the issue*
Since I couldn't implement my idea at the time, I just had to delete those pages. Now I have updated the "next" version, but I haven't tested it yet. If there are any updates, I'll let you know. I will also be glad to hear about any changes and, hopefully, successes.
Yep seems like generateStaticParams is not triggering for pages inside
@slot
Facing this issue, and end up by replace parallel route (@slot things) to layout.tsx.
AS-IS:
.
├── @slot1
│ ├── [slug]
│ │ └── page.tsx # generateStaticParams doesn't work as expected
│ └── page.tsx # export default () => null;
├── @slot2
│ └── default.tsx
├── layout.tsx # export default ({ slot1, slot2 }) => (<>{slot1}{slot2}</>);
└── page.tsx # export default () => null;
TO-BE:
.
├── [slug] # -> @slot1/[slug]
│ └── page.tsx # generateStaticParams works as expected
├── Slot2Component.tsx # -> @slot2/default.tsx
├── layout.tsx # export default ({ children }) => (<>{chidlren}<Slot2Component /></>);
└── page.tsx # export default () => null;
Actually I'm also using this with route group ((group) things) that can make multiple layout.tsx for single route.
This approach makes so many annoying folders and layout.tsx but yeah... works correctly at least
Still relevant on Next.js 14.
@Oleshkooo Unfortunately, this is not a minimal 


I made a fork of the official example nextgram so it's easier to reproduce, the project is already using "next": "canary".
My Fork:
git clone https://github.com/Jaycedam/nextgram.git
I added the same generateStaticParams() to the route and the modal (single commit). As mentioned in the issue, only the route is SSG, the modal is still dynamic.
Build result:
Route (app) Size First Load JS
┌ ○ / 6.94 kB 91.1 kB
├ ○ /_not-found 885 B 85.1 kB
├ λ /(.)photos/[id] 481 B 84.7 kB
├ ○ /default 143 B 84.3 kB
└ ● /photos/[id] 143 B 84.3 kB
├ /photos/1
├ /photos/2
├ /photos/3
└ [+3 more paths]
+ First Load JS shared by all 84.2 kB
├ chunks/69-ba1ea0421cdf6c89.js 28.9 kB
├ chunks/fd9d1056-0bb21fb122762d6f.js 53.4 kB
└ other shared chunks (total) 1.86 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
λ (Dynamic) server-rendered on demand using Node.js
@Jaycedam Thank you for sharing!
I can confirm that this is indeed an issue. We will be looking to fix this!
Reproducible in "next": "^14.1.2"
EDIT: I don't know if this is of any help, but here is some of my findings:
- This condition is not met: https://github.com/vercel/next.js/blob/6b111ec6d33d21dc9a47864dc5fa23ed120cb2a6/packages/next/src/build/index.ts#L2087
- During debugging it seemed like workerResult.prerenderRoutes has no objects in it.
- staticInfo a bit earlier in the code does say generateStaticParams: true
My page heavily uses parallel routing, which ultimately ends in just having dynamic rendering and no SSR at all.
So adding this feature would be really beneficial -> https://patrick-arns.de/
It seems that interception routes were deliberately excluded from static builds: https://github.com/vercel/next.js/pull/61004
I use a CDN in front of my self-hosted setup. This issue creates a problem when the parent page is statically rendered and thus cached by CDN, but the intercepted route (modal) isn't. Therefore after each deployment, I have to clear the full cache since the interception no longer works, and falls back to hard load.