Diizzayy / nuxt-graphql-client

⚡️ Minimal GraphQL Client + Code Generation for Nuxt3
https://nuxt-graphql-client.web.app
MIT License
366 stars 44 forks source link

No authorization headers sent after first load/hydration (client side navigation/HMR) #337

Open timchesney opened 1 year ago

timchesney commented 1 year ago

Environment


Describe the bug

First up - great package, super simple to get set up and an awesome DX improvement. Thanks!

I'm using nuxt-graphql-client with DatoCMS which requires a bearer token to be sent in the authorization header, with each request. This is very easy to set up with the package and, at first seemed to be working great.

runtimeConfig: {
    public: {
      'graphql-client': {
        clients: {
          default: {
            host: 'https://graphql.datocms.com/',
            token: process.env.DATO_CMS_API_KEY,
          },
        }
      }
    }
  },

When a page that requires data first loads, it works perfectly and sends the header with the request. However, if I use <NuxtLink> or navigateTo() to move to another page that uses data from the API, the client side request (e.g. post initial hydration) is sent without the Authorization header.

I have created a simple demo on CodeSandbox (missed the Stackblitz link until the end sorry!) just using a dummy Dato project and a limited read only key here: https://codesandbox.io/p/sandbox/nuxtgraphqlclient-nav-bug-pj98yk

Once the index has fully loaded (which takes a while given it's a pretty underpowered server), click one of the two links at the top which use data from Dato. The url will change but the page won't load. If you click the refresh button the page will load fine with the article title showing (e.g. SSR). Click to the second article will break again (post hydration client side). If you look at the request in dev tools, the client side request to Dato that doesn't contain the authorisation header.

--

Also this issue also happens when editing the <script setup>contents in an SFC. E.g. HMR runs and when it does the client side request to Dato is without the headers. If you edit the <template> contents it works as expected presumable because the data isn't re-fetched.

Expected behaviour

Navigating to a page with GraphQL data on it with NuxtLink or NavigateTo should make a client side request that includes the authorisation headers configued in NuxtConfig.

Reproduction

https://codesandbox.io/p/sandbox/nuxtgraphqlclient-nav-bug-pj98yk

Additional context

No response

Logs

No response

Attacler commented 1 year ago

@timchesney we are also experiencing this issue when using navigateTo, we use a hard redirect (window.location.href) to fix this for us

madebyfabian commented 1 year ago

Also experiencing this. I mean it kind of makes sense, since client navigation makes the useAsyncGql run on client only. And since the token is set to be server only by default. This doesn't work.

Possible Solutions:

Public Token (Potential Security Risk)

If your token is not secret, meaning that it can be exposed to public, you can use the retainToken method: https://nuxt-graphql-client.web.app/getting-started/configuration#retaintoken. Some CMS' like Storyblok allow this, some don't. When using WordPress with WPGraphQL for example, you should not do this, since it exposes your application password.

API Routes

You could build yourself a server/api/mypage.ts api route, that you call in your page/component. Since then, the call to graphql would always be from the server. This module does it automatically: https://github.com/dulnan/nuxt-graphql-middleware. Downside: Will be slower since you have effectively 2 api calls everytime you load data.

Nuxt Server Components

You could change your component to be a Nuxt Server Component. Keep in mind that this is still marked as experimental. See: https://roe.dev/blog/nuxt-server-components. To do this, add experimental: { componentIslands: true }, to your nuxt.config.ts. Then move your data loading with useAsyncGql into a component named like MyPage.server.vue. In your pages/my-page.vue, just add

<template>
  <MyPage />
</template>

But I think if you have interactivity in any child component, it might not work for you.

SSG

You could also move to static site generation. But this involves much more, and when you're dealing with data that frequently changes, it doesn't make sense.

Disable client side routing

Probably the most hacky one. Also has several performance downsides. Like @Attacler said, you could

Please let me know if you found another solution. I went with server components, which work for now.

timchesney commented 1 year ago

@madebyfabian Thanks for this reply. I had somehow missed the retainToken option (despite hunting around in the docs for a solution!). For my regular use case with DatoCMS, this solves the issue as the read-only key can be public and passed with front-end requests (much like StoryBlok's key). So that is very helpful. For other projects I will have a look into if server components could be a good work around for us.

Thanks again for the reply.

mcjkula commented 5 months ago

There has been many updates on the nuxt server components since that issue, and now in nuxt 3.11 it's possible to as well create server pages directly (https://nuxt.com/blog/v3-11#%EF%B8%8F-server-and-client-only-pages).

But I think if you have interactivity in any child component, it might not work for you.

This works now with the possibility to have client components in server components/pages (https://nuxt.com/docs/guide/directory-structure/components#client-components-within-server-components)