Skip to content

Island async component with Suspense causes fetch error on server side #353

@butameron

Description

@butameron

What version of HonoX are you using?

0.1.52

What steps can reproduce the bug?

Island async component with Suspense causes a fetch error on the server side because there is no "use client" or something similar equivalent.

The following code is pseudo code (because I already gave up and deleted the code).

some-page.tsx

<Suspense fallback={<Loading />}>
    <AsyncComponent />
</Suspense>

islands/async-component.tsx

export async function AsyncComponent() {
    const apiResult = await fetch("/api/something"); // throws invalid url error
    return (<>{apiResult}</>);
}

(If we pass a full URL to AsyncComponent, it will succeed, but the unwanted/unexpected server-side fetch may result in potential SSRF risks.)

What is the expected behavior?

fetch should not be executed in a server context.

What do you see instead?

Invalid url error on server console.

Additional information

In this case, the island component is expected to run in a client context because this <Suspense> only contains island components. However, <AsyncComponent /> also runs in the SSR server context and maybe just ignores useEffect. Therefore, an unwanted fetch runs in the server context.

However, it is not easy to avoid this, because <Suspense> is also used for streaming server-side fetch.

I also tried to create a wrapper island component like the following, but I can't stop the server-side fetch because <AsyncComponent /> is rendered in the page SSR context.

some-page.tsx

<Island fallback={<Loading />}>
  <AsyncComponent />
</Island>

islands/island.tsx

export function Island(~~~~) {
  if (typeof document === "undefined") return (<>{fallback}</>);
  const unwrapped = use(children); // or something (this may not work)
  return (<Suspense fallback={fallback}>{unwrapped}</Suspense>);
}

Maybe I should have added a server check inside AsyncComponent, but I gave up here and switched to full SSR.

If we simply want to fetch data on the client side and bind it to the UI, it would be much nicer if you could just write it as await fetch() in an async component.

I think honox is not React, so we need something like <SuspenseIsland> component that shows a fallback UI during hydration and while the client-side await is in progress, but does nothing on the server.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions