amannn / next-intl

🌐 Internationalization (i18n) for Next.js
https://next-intl-docs.vercel.app
MIT License
2.51k stars 230 forks source link

Compatibility of `now` with `experimental.dynamicIO` in Next.js canary versions #1464

Open typeofweb opened 6 days ago

typeofweb commented 6 days ago

Description

When dynamicIO is enabled, functions such as getMessages or getLocale cause next build to fail with the following error:

Error: Route "/_not-found" used new Date() instead of using performance or without explicitly calling await connection() beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time

Verifications

Mandatory reproduction URL

https://github.com/typeofweb/repro-nextintl

Reproduction description

Steps to reproduce:

  1. Run pnpm build.
  2. Observe the error.
  3. Remove usage of getMessages from layout.tsx.
  4. Run pnpm build.
  5. Observe no errors.

Expected behaviour

No errors.

amannn commented 6 days ago

That's interesting, thanks for the report!

The reason is that next-intl constructs a now value when the first component calls into i18n/request.ts (in your case through getMessages). This is in turn used for relative time formatting.

However, in your case (and probably many other apps), there's no need for relative time formatting.

Some potential solutions that come to my mind:

  1. Construct the date only when format.relativeTime(…) is called
  2. Remove a default for now generally, let the user provide this
  3. Make now mandatory for format.relativeTime(…) and remove this from i18n/request.ts

It seems like these approaches all have some drawbacks, e.g. not being able to forward now to the client side, requiring a bit more work for the user, etc.

This needs a bit further investigation. It's also still very early for PPR and dynamicIO, will have to see how the feature moves along. However, from the current perspective it seems like next-intl will have to adjust in some way.

Workaround

As a stopgap solution, you can put a 'use cache' at relevant places.

E.g. in i18n/request.ts:

import {getRequestConfig} from 'next-intl/server';

async function now() {
  'use cache';
  return new Date();
}

export default getRequestConfig(async () => ({
  locale: 'en-US',
  messages: {},
  now: await now()
}));

… or in entry points (like layout.tsx).

amannn commented 2 days ago

I just noticed that you can also add a function that provides now and internally has a 'use cache';. Still not ideal, but I believe a better workaround for the time being than having to decorate entry points like layout.tsx.