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

$fetch: use useRequestFetch() when calling internal API during SSR #24813

Open
3 of 4 tasks
Atinux opened this issue Dec 18, 2023 · 7 comments
Open
3 of 4 tasks

$fetch: use useRequestFetch() when calling internal API during SSR #24813

Atinux opened this issue Dec 18, 2023 · 7 comments

Comments

@Atinux
Copy link
Member

Atinux commented Dec 18, 2023

Describe the feature

When having API routes in Nuxt with authentication (using H3 useSession or nuxt-auth-utils), we need to forward the cookie to the API routes when fetching the data on SSR.

Not working

<script setup>
const { data: teams } = await useAsyncData('team', () => $fetch('/api/teams')
</script>

Working

<script setup>
const { data: teams } = await useAsyncData('team', () => useRequestFetch()('/api/teams')
</script>

Or using useFetch:

<script setup>
const { data: teams } = await useFetch('/api/teams', { key: 'teams' })
</script>

I believe it should work when we call our Nuxt API directly with $fetch to improve the DX.

Additional information

  • Would you be willing to help implement this feature?
  • Could this feature be implemented as a module?

Final checks

@pi0
Copy link
Member

pi0 commented Dec 21, 2023

This is a nice idea to improve DX, considering cases where users are directly using $fetch for this purpose of passing header however I'm afraid it reduces the portability of fetch and $fetch if we do an implicit behavior like this (ie: a utility can work in ambient context and plugins while broken in the lifecycle of a request with invalid headers or wise-versa also behavior in server-routes which worth to consider further discussion in Nitro if we wanna do something like this.)

I would suggest alternatively this to be shipped as part of factory function fetches. We could introduce also a preconfigured $apiFetch version that is intended for end-users explicitly and can contain additional logic like this. We can also introduce a new composable like $fetchWithHeaders that depends on context.

@danielroe
Copy link
Member

We agreed to defer discussion on this until #14736.

@blqke
Copy link

blqke commented Feb 22, 2024

Had this problem using lucia auth example, with this server middleware: https://github.com/lucia-auth/examples/blob/main/nuxt/github-oauth/server/middleware/auth.ts

when using $fetch inside useAsyncData,
event.context ⤵️

{
  _nitro: { routeRules: {} },
  nitro: { errors: [] },
  session: {
    id: 'something',
    userId: 'something',
    fresh: false,
    expiresAt: 2024-03-23T16:22:58.000Z
  },
  user: { githubId: undefined, username: 'something', id: 'something' },
  matchedRoute: { path: '/api/any-api-route', handlers: { get: [Function] } },
  params: {}
}

when using useRequestFetch() inside useAsyncData,
event.context ⤵️

{
  _nitro: { routeRules: {} },
  nitro: { errors: [] },
  session: null,
  user: null,
  matchedRoute: { path: '/api/any-api-route', handlers: { get: [Function] } },
  params: {}
}

I was trying to figure why $fetch wasn't including the cookie in the server request, and spent quite some time to find useRequestFetch as it is not documented yet (cf #19815). If you are using Lucia and you are having this H3Error: No session or user ID found problem, this is the solution!

@krthr
Copy link

krthr commented Feb 22, 2024

I had the same problem as @blqke. Because I didn't find info about useRequestFetch in the docs I did this:

export default defineNuxtRouteMiddleware(async (to, from) => {
  const authCookie = useCookie("auth").value;
  const user = useUser();

  const headers = new Headers();
  if (authCookie) {
    headers.set("Cookie", `auth=${authCookie}`);
  }

  const data = await $fetch("/api/auth/user", { headers });
  if (data?.user) {
    user.value = data.user;
  }
});

mentioning useRequestFetch in the docs could be very useful!

@vibonacci
Copy link

vibonacci commented May 12, 2024

useRequestFetch() only includes the browser cookies on the first SSR request. After hydration, the cookies are missing.

Also, this should really be documented.

n0099 added a commit to n0099/open-tbm that referenced this issue Jun 21, 2024
…g request @ `queryFunction()`

* rename `bodyText: string` to `responseBody: unknown` and serialize it before passing to parent ctor as the value of `Error.message`
@ class `FetchResponseError`
@ api/index.ts

* rename `NUXT_PUBLIC_API_BASE_URL` to `NUXT_PUBLIC_API_ENDPOINT_PREFIX` as it now should be an absoulte url due to nuxt/nuxt#24813 & https://github.com/nuxt/nuxt/blob/f2868f8c72320009e4502f97456b13f77c6b0322/packages/nuxt/src/app/composables/fetch.ts#L172-L178@ .env.example & nuxt.config.ts
@ fe
@danielroe danielroe removed the 3.x label Jun 30, 2024
@mohammadGh
Copy link

How to handle this issue more generally and what should be done when we don't use fetch directly?!

Assume that we are using an SDK function in useAsyncData and this function uses its own version of fetch. For example:

<script setup>
const { data: userProfile } = await useAsyncData('user-profile', () => $api.getCurrentUser())
</script>

Assuming that login has already been performed and the appropriate cookie exists, the above function works correctly on the client-side. However, it encounters problems during server-side rendering. As @Atinux sayed we need to forward the browser's cookies to the API routes when fetching the data (our performing any request) on SSR.

In general, any information present in the context of a page request, including headers and cookies, that triggers the SSR process should be propagated to every request made within that page using the fetch API.
This aligns with the developer's perspective since they perceive all fetch calls as originating from the browser, which automatically includes headers and cookies.

Dear @danielroe and @pi0 , I would appreciate your thoughts on this matter. Also, what are some current solutions for the example I mentioned? That is, when using an SDK and not having direct access to fetch, what should we do to avoid SSR issues?

@Degoya-Studio
Copy link

I can't seem to get the session on my server side.

<script setup lang="ts"> async function handleLogout() { await useFetch('/api/auth/logout', {method: 'GET'}) } </script>

export default defineEventHandler(async (event) => { await requireUserSession(event);

any idea of what I could be doing wrong here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Later
Development

No branches or pull requests

8 participants