Iam trying to figure out how to use new nextjs with its app router and streaming properly. And a little bit stuck. I have a simple page with a component surrounded by suspense. Component does a request to a throttled endpoint that takes 10 seconds to resolve. Then I'am adding a generateMetadata to the page and call notFound(). Browser immediately shows 404 page with a proper status code - thats okay. And then I expect that client side finishes to do things at that point, but it continues to await that 10s throttled data to stream. I feel that I miss something. Especially when doing curl you need to wait all 10 secs to receive that 404. Is there a way to abort streaming if it is already known at the beginning (metadata) that it will end up with 404 and no more data needs to be sent?
#Is there a way to abort streaming in nextjs14 while at generateMetadata stage?
29 messages · Page 1 of 1 (latest)
🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord
🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize
✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)
try this
const abortController = new AbortController();
export async function generateMetadata() {
abortController.abort();
notFound();
}
export default async function Page() {
const res = await fetch("", {
signal: abortController.signal,
cache: "no-store",
});
return <div></div>;
}
thanks fro reply! actually ive tried abort controller, but i made it in singleton module and imported to pass it to every fetch in project, so once it become aborted, none of the further fetches work) In your example I see an idea to create a new controller on a Page level, and then pass it to child components (that actually invoke fetches) with props?
btw, I think you could do this instead?
const findPage = cache(async () => {
return null;
});
export async function generateMetadata() {
const page = await findPage();
if (!page) notFound();
}
export default async function Page() {
const page = await findPage();
if (!page) notFound();
return <div></div>;
}
yep, iam actually doing that, but I have not the only one parallel fetch running at a time, and the other fetches wont stop
i hoped there is something like notFound({ killAll: true })
oh try this maybe and pass to child
sadly it wont work because abort controller persists
could you show some code on how you doing fetching?
I think this approach is better
gimme a minute i need to figure out how to post that shiny code snippets first
wrap the code with three `
const ac = new AbortController();
export async function generateMetadata(
{ params }: { params: {id: string}}
): Promise<Metadata> {
const { id } = params;
const result = await fetch1(ac)(id);
if (!result) {
ac.abort();
notFound();
}
return {
title: result.title,
}
}
export default function Page1({ params }: { params: { id: string } }) {
const { id } = params;
return (
<div className="flex h-screen flex-col">
<div className="grow">
<Suspense fallback={<Loader />}>
<Child1 id={id} ac={ac} />
</Suspense>
<Suspense fallback={<Loader />}>
<Child2 id={id} ac={ac} />
</Suspense>
</div>
</div>
);
}
export default async function Child1({ id, ac }: { id: string, ac: AbortController }) {
const result = await fetch1(ac)(id);
if (!result) {
return notFound();
}
return (<div>result.title</div>);
}
export default async function Child2({ id, ac }: { id: string, ac: AbortController }) {
const result = await fetch2(ac)(id);
if (!result) {
return (<div>Failed to fetch</div>);
}
return (<div>result.title</div>);
}
export const fetch1 = (abort: AbortController) => async (
id: string,
): Promise<Rec | null> => {
try {
const url = `${process.env.API_URL}/fetch1/${id}`;
const res = await fetch(url, { cache, signal: abort.signal });
// some code
} catch (e) {
return null;
}
};
export const fetch2 = (abort: AbortController) => async (
id: string,
): Promise<Rec | null> => {
try {
// THIS ONE TAKES 10s to load for the purpose of experiment
const url = `${process.env.API_URL}/fetch2/${id}`;
const res = await fetch(url, { cache, signal: abort.signal });
// some code
} catch (e) {
return null;
}
};
I think you just need to use the abort controller on fetch2 only
and wrap fetch1 with react cache like this
export const fetch1C = cache(fetch1);
right?
and now iam using fetch1C everywhere
yes
still same effect, after first invalid id and notFound-abort invocation, all previously opened valid pages become 404, because abortController stays the same on the client side and is already aborted as far as i understand
oh let me try
oh thank you, but i dont want you to waste your time on reproducing this, i still believe that there is some pattern exist... or iam using nextjs upside down
or render Child2 inside Child1
thats the point!
is that what you want?
yes! tested - this way now it works as I want! thank you! now i got what was wrong in my understanding of how things should be organized!