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

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

Open
1 task done
abstractvector opened this issue Jan 30, 2023 · 13 comments
Open
1 task done

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

abstractvector opened this issue Jan 30, 2023 · 13 comments
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template.

Comments

@abstractvector
Copy link
Contributor

Verify canary release

  • I verified that the issue exists in the latest Next.js 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 abstractvector added the bug Issue was opened via the bug report template. label Jan 30, 2023
@balazsorban44 balazsorban44 added the area: app App directory (appDir: true) label Jan 31, 2023
@abstractvector
Copy link
Contributor Author

This also happens using the new generateMetadata approach too. #41745 (reply in thread)

@abstractvector
Copy link
Contributor Author

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
Copy link

ayhanap commented Mar 12, 2023

I also experience the same issue.

@p00000001
Copy link

p00000001 commented Jun 21, 2023

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
Copy link

@huozhi in #45106 (comment) 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
Copy link

@Fredkiss3 #43548 (comment) 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
Copy link

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
Copy link

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
Copy link

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
Copy link

IVEN21 commented Apr 13, 2024

Any update? generateMetadata is blocking the UI

@mediabeastnz

This comment has been minimized.

@steve-marmalade
Copy link

@riley-worthington - thanks for sharing your solution.

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

@maxmdev
Copy link

maxmdev commented Jun 7, 2024

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

  • loading.tsx. should be updated and removed 'use client' from.

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 #45418 (comment).

Max

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template.
Projects
None yet
Development

No branches or pull requests

10 participants