Introducing the new Next.js and SWR clients
Tags: react, webdev, nextjs, programming
Published: 2022-12-10 03:04:27


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59rk5p5jty074x54xm25.png)

Our first Next.js client took a lot of inspiration from SWR, but we choose to build our own fetching library because SWR was lacking a couple of important features that we needed. Most notably built-in support for authentication, mutations (SWR is focused on fetching data, not updating it) and better SSR functionality. This suited us well at first, but along the way we realised that we had to build and maintain a lot of the same features that SWR already had. Why solve a problem that has already been solved?.

We started experimenting with using our TypeScript client together with SWR while building our cloud alpha. This turned out to be a great match, our TypeScript client is using fetch for making requests to the WunderGraph API, which is serving GraphQL operations over a JSON RPC style API and allows us to benefit from techniques like `stale-while-revalidate`. Which is exactly what SWR stands for.

Quickly after that we released our official integration with SWR, so we could start testing with the community. This was well received and it greatly improved the developer experience for our users. It gave us extra features like optimistic updates, invalidating queries, middleware support (more on this later) and it drastically reduced the amount of complexity in our code. This gave us the confidence to make SWR our default data fetching library for React and with the release of SWR 2.0RC it was time to rewrite our Next.js client to use SWR.

Now let's dive into the details of the new Next.js client. See what's changed and how you can use the new features.

This post applies to:

- `@wundergraph/nextjs` >= 0.5.0
- `@wundergraph/swr` >= 0.7.0
- `swr` >= 2.0.0-rc.0

## What's new

We tried to keep the new API similar to the previous, but there are some notable (breaking) changes.

The generated Next.js client no longer creates individual hooks for every operation, but instead we simply have 3 hooks, `useQuery`, `useSubscription` and `useMutation` that are fully typed. This makes the code very lightweight in production bundles while still benefiting from the full power of TypeScript.

### Queries

Instead of:

```ts
const { response, refetch } = useQuery.Weather({
  input: {
    forCity: 'Berlin',
  },
})
```

You can now write:

```ts
const { data, error, mutate } = useQuery({
  operationName: 'Weather',
  input: {
    forCity: 'Berlin',
  },
})
```

Conditionally fetching data is now also possible:

```ts
const { data: user } = useUser()

const { data, error, mutate } = useQuery({
  operationName: 'ProtectedWeather',
  input: {
    forCity: 'Berlin',
  },
  enabled: !!user,
})
```

Note that `refetch` has been renamed to `mutate` to be inline with the SWR API. Calling `mutate()` will invalidate the cache and refetch the query, you can also pass data to `mutate` to update the cache without refetching, this is useful for optimistic updates for example.

There is no `useLiveQuery` hook anymore, this is now just `useQuery` with the `liveQuery` option set to `true`, allowing you to easily turn every query into a live query.

```ts
const { data, error } = useQuery({
  operationName: 'Weather',
  input: {
    forCity: 'Berlin',
  },
  liveQuery: true,
})
```

### Subscriptions

Subscriptions have a similar API. Nothing significant has changed here, but you can now check if a subscription is being setup or is active with the `isLoading` and `isSubscribed` properties.

```ts
const { data, error, isLoading, isSubscribed } = useSubscription({
  operationName: 'Countdown',
  input: {
    from: 100,
  },
})
```

### Mutations

Mutations are also very similar to the previous API. `mutate` has been renamed to `trigger` to prevent confusion with the `mutate` function from SWR.

```ts
const { data, error, trigger } = useMutation({
  operationName: 'SetName',
})

// trigger is async by default
const result = await trigger({ name: 'Eelco' })

// prevent the promise from throwing
trigger({ name: 'Eelco' }, { throwOnError: false })
```

What's great about the new API is that it's now easier to invalidate queries after a mutation.

Let's say we have a query that fetches the current user's profile in one component and we have a form that updates the profile. We can add an `onSuccess` handler to the mutation that calls `mutate` (invalidate) on the `GetProfile` query.

```ts
const Profile = () => {
  const { data, error } = useQuery({
    operationName: 'GetProfile',
  })

  return <div>{data?.getProfile.name}</div>
}

const FormComponent = () => {
  const { mutate } = useSWRConfig()

  const { data, error, trigger } = useMutation({
    operationName: 'UpdateProfile',
    onSuccess() {
      // invalidate the query
      mutate({
        operationName: 'GetProfile',
      })
    },
  })

  const onSubmit = (event) => {
    e.preventDefault();
    const data = new FormData(event.target);
    trigger(data, { throwOnError: false })
  }

  return <form onSubmit={onSubmit}><input name="name" /><button type="submit">Save></button></form>
}
```

Now we could even make this fully optimistic by mutating the `GetProfile` cache instead and then refetching it, it would look something like this:

```ts
const FormComponent = () => {
  const { mutate } = useSWRConfig()

  const { data, error, trigger } = useMutation({
    operationName: 'UpdateProfile',
  })

  const onSubmit = (event) => {
    e.preventDefault();
    const data = new FormData(event.target);
    mutate(
      {
        operationName: 'GetProfile',
      },
      async () => {
        const result = await trigger(data)
        return {
          getProfile: result.updateProfile,
        }
      },
      {
        optimisticData: {
          getProfile: data,
        },
        rollbackOnError: true,
      }
    )
    trigger(data)
  }

  return <form onSubmit={onSubmit}><input name="name" /><button type="submit">Save></button></form>
}
```

## SSR

The new client also supports SSR and even works with live queries and subscriptions.

Simply wrap your `App` or `Page` with `withWunderGraph` and you're good to go.

```tsx
const HomePage = () => {
  const { data, error } = useQuery({
    operationName: 'Weather',
    input: {
      forCity: 'Berlin',
    },
    liveQuery: true,
  })

  return (
    <div>
      <h1>Weather</h1>
      <p>{data?.weather?.temperature}</p>
    </div>
  )
}

export default withWunderGraph(HomePage)
```

So how does this work?

Just like the old client we use `react-ssr-prepass` to render the component tree on the server inside `getInitialProps` and collect all fetch promises from the `useQuery` and `useSubscription` hooks and wait for them to resolve. This is then passed to the client, which is then passed to the `SWRConfig` [fallback option](https://swr.vercel.app/docs/with-nextjs). This is all done automatically, you don't have to do anything.

As I explained in the introduction, [SWR supports middlware](https://swr.vercel.app/docs/middleware), which is a really powerful feature and allows you to add functionality to individual hooks or globally using `SWRConfig`. The Next.js package uses middleware to add support for SSR to the hooks. Let's go a bit into to details how this works.

## Middleware

Our SSR middleware is added to `SWRConfig` internally by `withWunderGraph`, I'll go over the code here to explain how it works.

```ts
export const SSRMiddleWare = ((useSWRNext: SWRHook) => {
	return (key: Key, fetcher: BareFetcher<Promise<unknown>> | null, config: SSRConfig) => {
        // middleware logic
    }
})

<SWRConfig value={{ use: [SSRMiddleWare] }}>
  // your app
</SWRConfig>
```

This is how the logic works. First we check if this hook is being called on the server, if SSR is enabled and if it is a WunderGraph operation. If not we just return the swr hook.

```ts
const swr = useSWRNext(key, fetcher, config)

const context = useWunderGraphContext()

const isSSR =
  typeof window === 'undefined' && context?.ssr && config.ssr !== false

if (!isOperation(key) || !context || !isSSR || !key) {
  return swr
}
```

Now we serialize the key that we need for the ssrCache and to pass it down to the SWR `fallback`.
We check if this is an authenticated operation, if the user isn't logged in we don't need to execute the operation for SSR.

LiveQueries and subscriptions don't have a fetcher because data is loaded async, but for SSR we create a fetcher that will call the query with `subscribeOnce` set to true. This will return the subscription data directly instead of setting up an event stream, so we can server side render subscriptions ❤️.

```ts
const { operationName, input, liveQuery, subscription } = key

const _key = serialize(key)

const { ssrCache, client, user } = context

const shouldAuthenticate =
  client.isAuthenticatedOperation(operationName) && !user

let ssrFetcher = fetcher
if (!ssrFetcher && (liveQuery || subscription)) {
  ssrFetcher = async () => {
    const result = await client.query({
      operationName,
      input,
      subscribeOnce: true,
    })
    if (result.error) {
      throw result.error
    }
    return result.data
  }
}
```

Now we set the fetcher to our ssrCache with the serialized key. Which is then read in `getInitialProps`, returned to the client and passed to the `fallback` option of `SWRConfig` and SWR + Next.js do the rest.

```ts
if (ssrCache && !ssrCache[_key] && ssrFetcher && !shouldAuthenticate) {
  ssrCache[_key] = ssrFetcher(key)
}

return swr
```

## Resources

- [Next.js reference](https://docs.wundergraph.com/docs/clients-reference/nextjs)
- [SWR reference](https://docs.wundergraph.com/docs/clients-reference/swr)
- [Next.js example](https://docs.wundergraph.com/docs/examples/nextjs)

## Summary

So that's it, we are now using SWR as our default data fetching library, giving you all the benefits, like optimistic updates while consuming WunderGraph APIs.
It already improved our internal developer experience a lot and we are excited to hear about your experience with the new clients.

We're also interested in your experience about this topic in general.

- Are you using SWR? What do you think about it? What are your favorite features?
- Do you use other libraries for data fetching and cache management, like React Query? (hint; we will release a React Query integration soon).

Share it in the comments below or come join us on our [Discord server](https://wundergraph.com/discord).