vercel / next.js

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

Loading layer delayed with async head file (app dir) #45418

Open abstractvector opened 1 year ago

abstractvector commented 1 year ago

Verify canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 22.2.0: Fri Nov 11 02:08:47 PST 2022; root:xnu-8792.61.2~4/RELEASE_X86_64
Binaries:
  Node: 18.12.1
  npm: 8.19.2
  Yarn: N/A
  pnpm: N/A
Relevant packages:
  next: 13.1.6
  eslint-config-next: 13.1.6
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/abstractvector/nextjs-issue-async-head

To Reproduce

Create a head file in an appDir API route (e.g. ./src/app/blog/[slug]/head.tsx) that returns a default async function. Create a loading.tsx file or similar alongside it.

Describe the Bug

The loading component (e.g. ./src/app/blog/[slug]/loading.tsx) will not be displayed until the head component has rendered. Likewise, the URL will not be updated until the head component has rendered.

If both the head and page components are calling the same APIs to retrieve data (likely a common use case, and one specifically referenced in the documentation), then the loading layer will never show.

Expected Behavior

As soon as the user clicks the link, the URL should update and the loading layer should be displayed.

Which browser are you using? (if relevant)

Firefox 109.0

How are you deploying your application? (if relevant)

next start

abstractvector commented 1 year ago

This also happens using the new generateMetadata approach too. https://github.com/vercel/next.js/discussions/41745#discussioncomment-4886739

abstractvector commented 1 year ago

I've updated my reproduction repository to show the same behavior using the metadata / generateMetadata() architecture: https://github.com/abstractvector/nextjs-issue-async-head/tree/metadata

ayhanap commented 1 year ago

I also experience the same issue.

p00000001 commented 1 year ago

I'm having the same problem using the same API fetch for generateMetadata and the main page. Even for a relatively fast API fetch, the page still appears very slow without any loading.js to give an immediate UI response to the user's route change.

This behaviour is mentioned as intentional at: https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming

Next.js will wait for data fetching inside generateMetadata to complete before streaming UI to the client. This guarantees the first part of a streamed response includes tags.

Whilst this makes sense for the initial page load so the correct head elements are returned to crawlers etc., I would think subsequent navigations within the app shouldn't need to wait for generateMetadata to complete as the head could be updated at any point during a subsequent page load, which would allow loading.js to render immediately and give a much better UX.

Perhaps an option in next.config.js or an exported variable in page.js that changes the behaviour not to wait for generateMetadata to begin streaming, other then for the initial page load.

p00000001 commented 1 year ago

@huozhi in https://github.com/vercel/next.js/issues/45106#issuecomment-1440040679 on Feb 22 you mentioned in relation to this problem:

but we're also planning to patch the matdata separately to make navigation faster

Could you kindly advise if this patch has already been applied to the latest release or is still in the works? As the blocking generateMetadata problem still occurs, but I wasn't sure if we're still waiting on the patch or whether the patch didn't solve this particular issue. Thanks in advance

p00000001 commented 1 year ago

@Fredkiss3 https://github.com/vercel/next.js/issues/43548#issuecomment-1574138714 proposes a hack to return empty metadata for navigations after the initial load, and dynamically change the metadata in each page.js on the client side.

It may not be production-ready, but it would be good to see this sort of functionality available in the Next.js framework.

riley-worthington commented 8 months ago

Still having this issue in Next 14.1. My generateMetadata function makes an async call to get the name of the resource being shown on the page to show in the title, which prevents my loading.tsx skeleton screen from showing on page transitions until the request completes.

riley-worthington commented 8 months ago

In the meantime, here is my hacky solution inspired by @Fredkiss3

// getMetadataWithFallback.ts

import { Metadata } from 'next';
import { headers } from 'next/headers';

function isSSR() {
  return headers().get('accept')?.includes('text/html'); // for RSC navigations, it uses either `Accept: text/x-component` or `Accept: */*`, for SSR browsers and other client use `Accept: text/html`
}

const fallback: Metadata = {
  title: 'Loading...',
};

type GenerateMetadata<T> = (params: T) => Promise<Metadata>;

const getMetadataWithFallback =
  <Params>(
    generateMetadata: GenerateMetadata<Params>,
    staticMetadata?: Partial<Metadata>
  ) =>
  (params: Params) => {
    return isSSR()
      ? generateMetadata(params)
      : Promise.resolve({ ...fallback, ...staticMetadata });
  };

export default getMetadataWithFallback;

Usage:

// page.tsx

export const generateMetadata = getMetadataWithFallback(
    // your async generateMetadata function here
)

Finally, a hook to update the title from a client component once the data is fetched

// useUpdateTitle.ts

import { useLayoutEffect } from 'react';

export default function useUpdateTitle(title?: string) {
  useLayoutEffect(() => {
    if (title) {
      document.title = title;
    }
  }, [title]);
}
SSTPIERRE2 commented 6 months ago

Same issue here. Currently there appears to be no standard way of showing a loading state while generateMetadata is executing. I'll try the workaround above for now.

IVEN21 commented 6 months ago

Any update? generateMetadata is blocking the UI

mediabeastnz commented 5 months ago

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!

steve-marmalade commented 5 months ago

@riley-worthington - thanks for sharing your solution.

Do you mind explaining why useUpdateTitle.ts uses useLayoutEffect instead of useEffect?

maxmdev commented 4 months ago

Guys, I have found a fix for this whom may be looking:

Issue: loading.tsx not showing when metadata is fetching (showing blank instead of loading component).

If it uses client, it does not stream loading component from server when needed.

If you need somehow access path, etc. from request, use middleware + headers.

P/s: recommend to implement along with https://github.com/vercel/next.js/issues/45418#issuecomment-1914076788.

Max