Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix prerendered dynamic ISR functions for nested routes #900

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

rickmartensnl
Copy link

This expands on the change in #834 and #864 to fix prerendered routes with nested routes (e.g. [locale]/test/page.tsx -> nl/test and en/test).

I ran into an issue when adding localization with pre-rendered pages to my project on Cloudflare Pages. Everything generally worked, but dynamic routes with nested structures failed to build without export const runtime = 'edge';, which shouldn’t be required. This fix applies similar logic to handle nested dynamic ISR routes as with simple dynamic routes.

This PR fixes #899, if you would like to use the changes immediately the package is released on @rickmartensnl/next-on-pages.

Copy link

changeset-bot bot commented Oct 26, 2024

🦋 Changeset detected

Latest commit: 3e0119e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/next-on-pages Patch
eslint-plugin-next-on-pages Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderpan
Copy link

coderpan commented Nov 4, 2024

This expands on the change in #834 and #864 to fix prerendered routes with nested routes (e.g. [locale]/test/page.tsx -> nl/test and en/test).

I ran into an issue when adding localization with pre-rendered pages to my project on Cloudflare Pages. Everything generally worked, but dynamic routes with nested structures failed to build without export const runtime = 'edge';, which shouldn’t be required. This fix applies similar logic to handle nested dynamic ISR routes as with simple dynamic routes.

This PR fixes #899, if you would like to use the changes immediately the package is released on @rickmartensnl/next-on-pages.

I have used your npm package to replace cf/next-on-pages, but the error appear again
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"pages:build": "npx @rickmartensnl/next-on-pages",
"preview": "npm run pages:build && wrangler pages dev",
"deploy": "npm run pages:build && wrangler pages deploy"
},

npm run preview

⚡️ ERROR: Failed to produce a Cloudflare Pages build from the project.
⚡️
⚡️ The following routes were not configured to run with the Edge Runtime:
⚡️ - /[locale]
⚡️
⚡️ Please make sure that all your non-static routes export the following edge runtime route segment config:
⚡️ export const runtime = 'edge';
⚡️
⚡️ You can read more about the Edge Runtime on the Next.js documentation:
⚡️ https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes

@rickmartensnl
Copy link
Author

The issue with /[locale] should have already been fixed in cloudflare’s package. Also make sure you have replaced everything with the package. Including the dependencies.

@coderpan
Copy link

coderpan commented Nov 4, 2024

The issue with /[locale] should have already been fixed in cloudflare’s package. Also make sure you have replaced everything with the package. Including the dependencies.

Thank you, i have remove node_modules, but the error appear again.

npm install
npm run build

here is my package.json

{
  "name": "hello-pages",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "pages:build": "npx @rickmartensnl/next-on-pages",
    "preview": "npm run pages:build && wrangler pages dev",
    "deploy": "npm run pages:build && wrangler pages deploy"
  },
  "dependencies": {
    "next": "14.2.5",
    "next-intl": "^3.24.0",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@rickmartensnl/next-on-pages": "^1.13.5",
    "@cloudflare/workers-types": "^4.20241022.0",
    "eslint": "^8",
    "eslint-config-next": "14.2.5",
    "eslint-plugin-next-on-pages": "^1.13.5",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "vercel": "^37.14.0",
    "wrangler": "^3.84.1"
  }
}

@rickmartensnl
Copy link
Author

Mind sharing your page.tsx of the /[locale] route?

@coderpan
Copy link

coderpan commented Nov 4, 2024

Mind sharing your page.tsx of the /[locale] route?

Of course, I have been troubled by this question for many days.

src/app/[locale]/layout.js

import { Inter } from "next/font/google";
import "@/app/globals.css";
import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';
import { getTranslations } from 'next-intl/server';
import { locales } from '@/i18n/config';
const inter = Inter({ subsets: ["latin"] });

export const dynamicParams = false;

export async function generateMetadata({ params: { locale } }) {
  const t = await getTranslations({ locale, namespace: 'metadata' });
  
  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      languages: {
        'en': '/en',
        'zh': '/zh',
      },
    },
  };
}

export function generateStaticParams() {
    return locales.map((locale) => ({ locale }));
}

export default async function RootLayout({ children, params: { locale } }) {
  let messages;
  try {
    messages = (await import(`@/messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }

  return (
    <html lang={locale}>
      <head>
        <link rel="alternate" href="/en" hrefLang="en" />
        <link rel="alternate" href="/zh" hrefLang="zh" />
        <link rel="alternate" href="/en" hrefLang="x-default" />
      </head>
      <body className={inter.className}>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

src/app/[locale]/page.js

import { useTranslations } from 'next-intl';
import Image from "next/image";
import LanguageSwitcher from '@/components/LanguageSwitcher';

//export const dynamicParams = false;

export default function Home() {
  const t = useTranslations('home');

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <LanguageSwitcher />
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
          {t('title')}&nbsp;
          <code className="font-mono font-bold">src/app/page.js</code>
        </p>
      </div>
    </main>
  );
}

@rickmartensnl
Copy link
Author

I use the same library in my project, it has to do with some static rendering.

For every page you need to set the request locale using setRequestLocale(locale);

https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering.

Keep in mind that:

  1. The locale that you pass to setRequestLocale should be validated (e.g. in your root layout).
  2. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.
  3. setRequestLocale needs to be called before you invoke any functions from next-intl like useTranslations or getMessages.

On top of that I would suggest you to use in the layout this instead:

export default async function RootLayout({ children, params: { locale } }) {
  if (!locale || !routing.locales.includes(locale as any)) {
    notFound();
  }

  setRequestLocale(locale);

  const messages = await getMessages();
  
  return (
    <html lang={locale}>
      <head>
        <link rel="alternate" href="/en" hrefLang="en" />
        <link rel="alternate" href="/zh" hrefLang="zh" />
        <link rel="alternate" href="/en" hrefLang="x-default" />
      </head>
      <body className={inter.className}>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

@coderpan
Copy link

coderpan commented Nov 4, 2024

I use the same library in my project, it has to do with some static rendering.

For every page you need to set the request locale using setRequestLocale(locale);

https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering.

Keep in mind that:

  1. The locale that you pass to setRequestLocale should be validated (e.g. in your root layout).
  2. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.
  3. setRequestLocale needs to be called before you invoke any functions from next-intl like useTranslations or getMessages.

On top of that I would suggest you to use in the layout this instead:

export default async function RootLayout({ children, params: { locale } }) {
  if (!locale || !routing.locales.includes(locale as any)) {
    notFound();
  }

  setRequestLocale(locale);

  const messages = await getMessages();
  
  return (
    <html lang={locale}>
      <head>
        <link rel="alternate" href="/en" hrefLang="en" />
        <link rel="alternate" href="/zh" hrefLang="zh" />
        <link rel="alternate" href="/en" hrefLang="x-default" />
      </head>
      <body className={inter.className}>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Thank you for pointing out the issue with setRequestLocale, which can lead to incorrect locale during SSR, but the Edge Runtime issue still persists.

Route (app)                              Size     First Load JS
▲  ┌ ○ /                                    146 B          87.3 kB
▲  ├ ƒ /_not-found                          146 B          87.3 kB
▲  ├ ● /[locale]                            27.4 kB         115 kB
▲  ├   ├ /en
▲  ├   └ /zh
▲  └ ƒ /api/hello                           0 B                0 B
▲  + First Load JS shared by all            87.1 kB
▲  ├ chunks/526-70de53c2f5e51d26.js       31.6 kB
▲  ├ chunks/fd9d1056-2821b0f0cabcd8bd.js  53.6 kB
▲  └ other shared chunks (total)          1.92 kB
▲  
▲  
▲  ƒ Middleware                             39.6 kB
▲  ○  (Static)   prerendered as static content
▲  ●  (SSG)      prerendered as static HTML (uses getStaticProps)
▲  ƒ  (Dynamic)  server-rendered on demand
▲  Traced Next.js server files in: 462.633ms
▲  Created all serverless functions in: 69.368ms
▲  Collected static files (public/, static/, .next/static): 32.365ms
▲  Build Completed in .vercel/output [30s]
⚡️ Completed `npx vercel build`.

⚡️ ERROR: Failed to produce a Cloudflare Pages build from the project.
⚡️ 
⚡️      The following routes were not configured to run with the Edge Runtime:
⚡️        - /[locale]
⚡️ 
⚡️      Please make sure that all your non-static routes export the following edge runtime route segment config:
⚡️        export const runtime = 'edge';
⚡️ 
⚡️      You can read more about the Edge Runtime on the Next.js documentation:
⚡️        https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes

@rickmartensnl
Copy link
Author

The setRequestLocale(locale); also has to be in every page and layout you want to use static rendering.

  1. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[🐛 Bug]: generateStaticParams forces to be on the edge on nested routes.
2 participants