vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.1k stars 27k forks source link

Dynamic intercepting parallel route is server side rendered #52842

Open oleshkooo opened 1 year ago

oleshkooo commented 1 year ago

Verify 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)

NEXT-2808

lukasjoho commented 1 year ago

+1 on this! @Oleshkooo Have you found the issue here?

Jordaneisenburger commented 1 year ago

Yep seems like generateStaticParams is not triggering for pages inside @slot

oleshkooo commented 1 year ago

+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.

ramram1048 commented 1 year ago

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

Raberrse commented 10 months ago

Still relevant on Next.js 14.

samcx commented 10 months ago

@Oleshkooo Unfortunately, this is not a minimal :repro: that we can take look. If you can create a minimal :public-big: :repro:, we will be able to take a look at this!

Jaycedam commented 9 months ago

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
samcx commented 9 months ago

@Jaycedam Thank you for sharing!

I can confirm that this is indeed an issue. We will be looking to fix this!

KoenLemmen commented 8 months ago

Reproducible in "next": "^14.1.2"

EDIT: I don't know if this is of any help, but here is some of my findings:

PArns commented 7 months ago

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/

fmnxl commented 4 months ago

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.

dev-aly3n commented 2 weeks ago

facing the same issue

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

claygiffin commented 1 week ago

Is there any progress on this? I am trying to build a site that has a few modals with static content from a CMS, but they feel really sluggish due to being dynamically generated.