Gathering detailed insights and metrics for @apollo/client-react-streaming
Gathering detailed insights and metrics for @apollo/client-react-streaming
Gathering detailed insights and metrics for @apollo/client-react-streaming
Gathering detailed insights and metrics for @apollo/client-react-streaming
Apollo Client support for the Next.js App Router
npm install @apollo/client-react-streaming
v.0.11.5: fix a bundling issue
Published on 13 Nov 2024
v.0.11.5: Next.js 15
Published on 22 Oct 2024
v.0.11.4: warn on `ssrMode`, more resilient data transport
Published on 21 Oct 2024
v.0.11.3: remove `queryRef.toPromise()`, lazily load `SimulatePreloadedQuery`
Published on 02 Sept 2024
v0.11.2: export additional `PreloadQuery`-related types
Published on 12 Jun 2024
v0.11.1: type fix for `PreloadQuery`
Published on 10 Jun 2024
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
459 Stars
312 Commits
35 Forks
18 Watching
30 Branches
33 Contributors
Updated on 28 Nov 2024
TypeScript (81.49%)
JavaScript (17.09%)
CSS (1.22%)
HTML (0.2%)
Cumulative downloads
Total Downloads
Last day
-3.1%
17,768
Compared to previous day
Last week
2.7%
92,252
Compared to previous week
Last month
16.9%
371,625
Compared to previous month
Last year
0%
1,895,673
Compared to previous year
1
2
30
❗️ This package is experimental.
Generally it should work well, you might run into race conditions when your Client Component is still rendering in SSR, and already making overlapping queries on the browser.
This cannot be addressed from our side, but would need API changes in Next.js or React itself.
If you do not use suspense in your application, this will not be a problem to you.
☑️ Apollo Client User Survey |
---|
What do you like best about Apollo Client? What needs to be improved? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better. |
You can find a detailed technical breakdown of what this package does and why it needs to do so in the discussion around the accompanying RFC.
If you want to use Apollo Client in your Next.js app with React Server Components, you will need a way of creating a client instance that is shared between all your server components for one request to prevent making duplicate requests.
When using the app
directory, all your "client components" will not only run in the browser. They will also be rendered on the server - in an "SSR" run that will execute after React Server Components have been rendered.
If you want to make the most of your application, you probably already want to make your GraphQL requests on the server so that the page is fully rendered when it reaches the browser.
This package provides the tools necessary to execute your GraphQL queries on the server and to use the results to hydrate your browser-side cache and components.
This package has a peer dependency on the latest @apollo/client
, so you can install both this package and that Apollo Client version via
1npm install @apollo/client@latest @apollo/experimental-nextjs-app-support
❗️ We do handle "RSC" and "SSR" use cases as completely separate.
You should generally try not to have overlapping queries between the two, as all queries made in SSR can dynamically update in the browser as the cache updates (e.g. from a mutation or another query), but queries made in RSC will not be updated in the browser - for that purpose, the full page would need to rerender. As a result, any overlapping data would result in inconsistencies in your UI.
So decide for yourself, which queries you want to make in RSC and which in SSR, and don't have them overlap.
Create an ApolloClient.js
file:
1import { HttpLink } from "@apollo/client";
2import {
3 registerApolloClient,
4 ApolloClient,
5 InMemoryCache,
6} from "@apollo/experimental-nextjs-app-support";
7
8export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
9 return new ApolloClient({
10 cache: new InMemoryCache(),
11 link: new HttpLink({
12 // this needs to be an absolute url, as relative urls cannot be used in SSR
13 uri: "http://example.com/api/graphql",
14 // you can disable result caching here if you want to
15 // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
16 // fetchOptions: { cache: "no-store" },
17 }),
18 });
19});
You can then use that getClient
function in your server components:
1const { data } = await getClient().query({ query: userQuery }); 2// `query` is a shortcut for `getClient().query` 3const { data } = await query({ query: userQuery });
For a description of PreloadQuery
, see Preloading data in RSC for usage in Client Components
If you use the app
directory, each Client Component will be SSR-rendered for the initial request. So you will need to use this package.
First, create a new file app/ApolloWrapper.jsx
:
1"use client";
2// ^ this file needs the "use client" pragma
3
4import { HttpLink } from "@apollo/client";
5import {
6 ApolloNextAppProvider,
7 ApolloClient,
8 InMemoryCache,
9} from "@apollo/experimental-nextjs-app-support";
10
11// have a function to create a client for you
12function makeClient() {
13 const httpLink = new HttpLink({
14 // this needs to be an absolute url, as relative urls cannot be used in SSR
15 uri: "https://example.com/api/graphql",
16 // you can disable result caching here if you want to
17 // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
18 fetchOptions: { cache: "no-store" },
19 // you can override the default `fetchOptions` on a per query basis
20 // via the `context` property on the options passed as a second argument
21 // to an Apollo Client data fetching hook, e.g.:
22 // const { data } = useSuspenseQuery(MY_QUERY, { context: { fetchOptions: { cache: "force-cache" }}});
23 });
24
25 // use the `ApolloClient` from "@apollo/experimental-nextjs-app-support"
26 return new ApolloClient({
27 // use the `InMemoryCache` from "@apollo/experimental-nextjs-app-support"
28 cache: new InMemoryCache(),
29 link: httpLink,
30 });
31}
32
33// you need to create a component to wrap your app in
34export function ApolloWrapper({ children }: React.PropsWithChildren) {
35 return (
36 <ApolloNextAppProvider makeClient={makeClient}>
37 {children}
38 </ApolloNextAppProvider>
39 );
40}
Now you can wrap your RootLayout
in this wrapper component:
1import { ApolloWrapper } from "./ApolloWrapper"; 2 3// ... 4 5export default function RootLayout({ 6 children, 7}: { 8 children: React.ReactNode, 9}) { 10 return ( 11 <html lang="en"> 12 <body> 13 <ApolloWrapper>{children}</ApolloWrapper> 14 </body> 15 </html> 16 ); 17}
☝️ This will work even if your layout is a React Server Component and will also allow the children of the layout to be React Server Components.
It just makes sure that all Client Components will have access to the same Apollo Client instance, shared through theApolloNextAppProvider
.
If you want to make the most of the streaming SSR features offered by React & the Next.js App Router, consider using the useSuspenseQuery
and useFragment
hooks.
Starting with version 0.11, you can preload data in RSC to populate the cache of your Client Components.
For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs. Then you can use the PreloadQuery
component in your React Server Components:
1<PreloadQuery 2 query={QUERY} 3 variables={{ 4 foo: 1, 5 }} 6> 7 <Suspense fallback={<>loading</>}> 8 <ClientChild /> 9 </Suspense> 10</PreloadQuery>
And you can use useSuspenseQuery
in your ClientChild
component with the same QUERY and variables:
1"use client"; 2 3import { useSuspenseQuery } from "@apollo/client"; 4// ... 5 6export function ClientChild() { 7 const { data } = useSuspenseQuery(QUERY, { variables: { foo: 1 } }); 8 return <div>...</div>; 9}
[!TIP] The
Suspense
boundary here is optional and only for demonstration purposes to show that something suspenseful is going on.
PlaceSuspense
boundaries at meaningful places in your UI, where they give your users the best user experience.
This example will fetch a query in RSC, and then transport the data into the Client Component cache.
Before the child ClientChild
in the example renders, a "simulated network request" for this query is started in your Client Components.
That way, if you repeat the query in your Client Component using useSuspenseQuery
(or even useQuery
!), it will wait for the network request in your Server Component to finish instead of making it's own network request.
[!IMPORTANT] Keep in mind that we don't recommend mixing data between Client Components and Server Components. Data fetched this way should be considered client data and never be referenced in your Server Components.
PreloadQuery
prevents mixing server data and client data by creating a separateApolloClient
instance using themakeClient
function passed intoregisterApolloClient
.
useReadQuery
Just like using useBackgroundQuery
with useReadQuery
in place of useSuspenseQuery
to avoid request waterfalls, you can also use PreloadQuery
in combination with useReadQuery
in Client Components to achieve a similar result. Use the render prop notation to get a QueryRef
that you can pass to your Client Component:
1<PreloadQuery 2 query={QUERY} 3 variables={{ 4 foo: 1, 5 }} 6> 7 {(queryRef) => ( 8 <Suspense fallback={<>loading</>}> 9 <ClientChild queryRef={queryRef} /> 10 </Suspense> 11 )} 12</PreloadQuery>
Inside of ClientChild
, you could then call useReadQuery
with the queryRef
prop.
1"use client"; 2 3import { useQueryRefHandlers, useReadQuery, QueryRef } from "@apollo/client"; 4 5export function ClientChild({ queryRef }: { queryRef: QueryRef<TQueryData> }) { 6 const { refetch } = useQueryRefHandlers(queryRef); 7 const { data } = useReadQuery(queryRef); 8 return <div>...</div>; 9}
[!TIP] The
Suspense
boundary here is optional and only for demonstration purposes to show that something suspenseful is going on.
PlaceSuspense
boundaries at meaningful places in your UI, where they give your users the best user experience.
Keep in mind that this will look like a "current network request" to your Client Component and as such will update data that is already in your Client Component cache, so make sure that the data you pass from your Server Components is not outdated, e.g. because of other caching layers you might be using, like the Next.js fetch cache.
This package uses some singleton instances on the Browser side - if you are writing tests, you must reset them between tests.
For that, you can use the resetApolloClientSingletons
helper:
1import { resetApolloClientSingletons } from "@apollo/experimental-nextjs-app-support"; 2 3afterEach(resetApolloClientSingletons);
Generally, useSuspenseQuery
will only suspend until the initial response is received.
In most cases, you get a full response, but if you use multipart response features like the @defer
directive, you will only get a partial response.
Without further handling, your component will now render with partial data - but the request will still keep running in the background. This is a worst-case scenario because your server will have to bear the load of that request, but the client will not get the complete data anyways.
To handle this, you can apply one of two different strategies:
@defer
fragments from your queryFor this, we ship the two links RemoveMultipartDirectivesLink
and AccumulateMultipartResponsesLink
, as well as the SSRMultipartLink
, which combines both of them into a more convenient-to-use Link.
You can also check out the Hack The Supergraph example, which shows this in use and allows you to adjust the speed deferred interfaces resolve in.
@defer
fragments from your query with RemoveMultipartDirectivesLink
Usage example:
1new RemoveMultipartDirectivesLink({
2 /**
3 * Whether to strip fragments with `@defer` directives
4 * from queries before sending them to the server.
5 *
6 * Defaults to `true`.
7 *
8 * Can be overwritten by adding a label starting
9 * with either `"SsrDontStrip"` or `"SsrStrip"` to the
10 * directive.
11 */
12 stripDefer: true,
13});
This link will (if called with stripDefer: true
) strip all @defer
fragments from your query.
You can exclude certain fragments from this behavior by giving them a label starting with "SsrDontStrip"
.
Example:
1query myQuery { 2 fastField 3 ... @defer(label: "SsrDontStrip1") { 4 slowField1 5 } 6 ... @defer(label: "SsrDontStrip2") { 7 slowField2 8 } 9}
You can also use the link with stripDefer: false
and mark certain fragments to be stripped by giving them a label starting with "SsrStrip"
.
AccumulateMultipartResponsesLink
Usage example:
1new AccumulateMultipartResponsesLink({
2 /**
3 * The maximum delay in milliseconds
4 * from receiving the first response
5 * until the accumulated data will be flushed
6 * and the connection will be closed.
7 */
8 cutoffDelay: 100,
9});
This link can be used to "debounce" the initial response of a multipart request. Any incremental data received during the cutoffDelay
time will be merged into the initial response.
After cutoffDelay
, the link will return the initial response, even if there is still incremental data pending, and close the network connection.
If cutoffDelay
is 0
, the link will immediately return data as soon as it is received, without waiting for incremental data, and immediately close the network connection.
SSRMultipartLink
Usage example:
1new SSRMultipartLink({
2 /**
3 * Whether to strip fragments with `@defer` directives
4 * from queries before sending them to the server.
5 *
6 * Defaults to `true`.
7 *
8 * Can be overwritten by adding a label starting
9 * with either `"SsrDontStrip"` or `"SsrStrip"` to the
10 * directive.
11 */
12 stripDefer: true,
13 /**
14 * The maximum delay in milliseconds
15 * from receiving the first response
16 * until the accumulated data will be flushed
17 * and the connection will be closed.
18 *
19 * Defaults to `0`.
20 */
21 cutoffDelay: 100,
22});
This link combines the behavior of RemoveMultipartDirectivesLink
and AccumulateMultipartResponsesLink
into a single link.
If you want more information on what data is sent over the wire, enable logging in your app/ApolloWrapper.ts
:
1import { setVerbosity } from "ts-invariant"; 2setVerbosity("debug");
No vulnerabilities found.
No security vulnerabilities found.