@apollo/client 4.2.0-alpha.2 → 4.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/__cjs/core/ApolloClient.cjs +9 -9
- package/__cjs/core/ApolloClient.cjs.map +1 -1
- package/__cjs/core/ApolloClient.d.cts +131 -21
- package/__cjs/link/ws/index.cjs +9 -1
- package/__cjs/link/ws/index.cjs.map +1 -1
- package/__cjs/link/ws/index.d.cts +1 -1
- package/__cjs/react/hooks/useBackgroundQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useBackgroundQuery.d.cts +1466 -65
- package/__cjs/react/hooks/useLazyQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useLazyQuery.d.cts +346 -39
- package/__cjs/react/hooks/useLoadableQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useLoadableQuery.d.cts +492 -49
- package/__cjs/react/hooks/useMutation.cjs +5 -48
- package/__cjs/react/hooks/useMutation.cjs.map +1 -1
- package/__cjs/react/hooks/useMutation.d.cts +239 -130
- package/__cjs/react/hooks/useQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useQuery.d.cts +570 -40
- package/__cjs/react/hooks/useSubscription.cjs +1 -1
- package/__cjs/react/hooks/useSubscription.cjs.map +1 -1
- package/__cjs/react/hooks/useSubscription.d.cts +2 -2
- package/__cjs/react/hooks/useSuspenseQuery.cjs.map +1 -1
- package/__cjs/react/hooks/useSuspenseQuery.d.cts +734 -45
- package/__cjs/version.cjs +1 -1
- package/core/ApolloClient.d.ts +131 -21
- package/core/ApolloClient.js +9 -9
- package/core/ApolloClient.js.map +1 -1
- package/link/ws/index.d.ts +1 -1
- package/link/ws/index.js +9 -1
- package/link/ws/index.js.map +1 -1
- package/package.json +3 -7
- package/react/hooks/useBackgroundQuery.d.ts +1466 -65
- package/react/hooks/useBackgroundQuery.js.map +1 -1
- package/react/hooks/useLazyQuery.d.ts +346 -39
- package/react/hooks/useLazyQuery.js.map +1 -1
- package/react/hooks/useLoadableQuery.d.ts +492 -49
- package/react/hooks/useLoadableQuery.js.map +1 -1
- package/react/hooks/useMutation.d.ts +239 -130
- package/react/hooks/useMutation.js +5 -48
- package/react/hooks/useMutation.js.map +1 -1
- package/react/hooks/useQuery.d.ts +570 -40
- package/react/hooks/useQuery.js.map +1 -1
- package/react/hooks/useSubscription.d.ts +2 -2
- package/react/hooks/useSubscription.js +1 -1
- package/react/hooks/useSubscription.js.map +1 -1
- package/react/hooks/useSuspenseQuery.d.ts +734 -45
- package/react/hooks/useSuspenseQuery.js.map +1 -1
- package/react/hooks-compiled/useBackgroundQuery.d.ts +1466 -65
- package/react/hooks-compiled/useBackgroundQuery.js.map +1 -1
- package/react/hooks-compiled/useLazyQuery.d.ts +346 -39
- package/react/hooks-compiled/useLazyQuery.js.map +1 -1
- package/react/hooks-compiled/useLoadableQuery.d.ts +492 -49
- package/react/hooks-compiled/useLoadableQuery.js.map +1 -1
- package/react/hooks-compiled/useMutation.d.ts +239 -130
- package/react/hooks-compiled/useMutation.js +4 -47
- package/react/hooks-compiled/useMutation.js.map +1 -1
- package/react/hooks-compiled/useQuery.d.ts +570 -40
- package/react/hooks-compiled/useQuery.js.map +1 -1
- package/react/hooks-compiled/useSubscription.d.ts +2 -2
- package/react/hooks-compiled/useSubscription.js +1 -1
- package/react/hooks-compiled/useSubscription.js.map +1 -1
- package/react/hooks-compiled/useSuspenseQuery.d.ts +734 -45
- package/react/hooks-compiled/useSuspenseQuery.js.map +1 -1
- package/skills/apollo-client/SKILL.md +168 -0
- package/skills/apollo-client/references/caching.md +560 -0
- package/skills/apollo-client/references/error-handling.md +350 -0
- package/skills/apollo-client/references/fragments.md +804 -0
- package/skills/apollo-client/references/integration-client.md +336 -0
- package/skills/apollo-client/references/integration-nextjs.md +325 -0
- package/skills/apollo-client/references/integration-react-router.md +256 -0
- package/skills/apollo-client/references/integration-tanstack-start.md +378 -0
- package/skills/apollo-client/references/mutations.md +549 -0
- package/skills/apollo-client/references/queries.md +416 -0
- package/skills/apollo-client/references/state-management.md +428 -0
- package/skills/apollo-client/references/suspense-hooks.md +773 -0
- package/skills/apollo-client/references/troubleshooting.md +487 -0
- package/skills/apollo-client/references/typescript-codegen.md +133 -0
- package/version.js +1 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Apollo Client Integration for Client-Side Apps
|
|
2
|
+
|
|
3
|
+
This guide covers setting up Apollo Client in client-side React applications without server-side rendering (SSR). This includes applications using Vite, Parcel, Create React App, or other bundlers that don't implement SSR.
|
|
4
|
+
|
|
5
|
+
For applications with SSR, use one of the framework-specific integration guides instead:
|
|
6
|
+
|
|
7
|
+
- [Next.js App Router](integration-nextjs.md)
|
|
8
|
+
- [React Router Framework Mode](integration-react-router.md)
|
|
9
|
+
- [TanStack Start](integration-tanstack-start.md)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @apollo/client graphql rxjs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## TypeScript Code Generation (optional but recommended)
|
|
18
|
+
|
|
19
|
+
For type-safe GraphQL operations with TypeScript, see the [TypeScript Code Generation guide](typescript-codegen.md).
|
|
20
|
+
|
|
21
|
+
## Setup Steps
|
|
22
|
+
|
|
23
|
+
### Step 1: Create Client
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
|
|
27
|
+
|
|
28
|
+
// Recommended: Use HttpOnly cookies for authentication
|
|
29
|
+
const httpLink = new HttpLink({
|
|
30
|
+
uri: "https://your-graphql-endpoint.com/graphql",
|
|
31
|
+
credentials: "include", // Sends cookies with requests (secure when using HttpOnly cookies)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const client = new ApolloClient({
|
|
35
|
+
link: httpLink,
|
|
36
|
+
cache: new InMemoryCache(),
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If you need manual token management (less secure, only when HttpOnly cookies aren't available):
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
|
|
44
|
+
import { SetContextLink } from "@apollo/client/link/context";
|
|
45
|
+
|
|
46
|
+
const httpLink = new HttpLink({
|
|
47
|
+
uri: "https://your-graphql-endpoint.com/graphql",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const authLink = new SetContextLink(({ headers }) => {
|
|
51
|
+
const token = localStorage.getItem("token");
|
|
52
|
+
return {
|
|
53
|
+
headers: {
|
|
54
|
+
...headers,
|
|
55
|
+
authorization: token ? `Bearer ${token}` : "",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const client = new ApolloClient({
|
|
61
|
+
link: authLink.concat(httpLink),
|
|
62
|
+
cache: new InMemoryCache(),
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Step 2: Setup Provider
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { ApolloProvider } from "@apollo/client";
|
|
70
|
+
import App from "./App";
|
|
71
|
+
|
|
72
|
+
function Root() {
|
|
73
|
+
return (
|
|
74
|
+
<ApolloProvider client={client}>
|
|
75
|
+
<App />
|
|
76
|
+
</ApolloProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 3: Execute Query
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { gql } from "@apollo/client";
|
|
85
|
+
import { useQuery } from "@apollo/client/react";
|
|
86
|
+
|
|
87
|
+
const GET_USERS = gql`
|
|
88
|
+
query GetUsers {
|
|
89
|
+
users {
|
|
90
|
+
id
|
|
91
|
+
name
|
|
92
|
+
email
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
function UserList() {
|
|
98
|
+
const { loading, error, data, dataState } = useQuery(GET_USERS);
|
|
99
|
+
|
|
100
|
+
if (loading) return <p>Loading...</p>;
|
|
101
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
102
|
+
|
|
103
|
+
// TypeScript note: for stricter type narrowing, you can also check `dataState === "complete"` before accessing data
|
|
104
|
+
return (
|
|
105
|
+
<ul>{data?.users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Basic Query Usage
|
|
111
|
+
|
|
112
|
+
### Using Variables
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
const GET_USER = gql`
|
|
116
|
+
query GetUser($id: ID!) {
|
|
117
|
+
user(id: $id) {
|
|
118
|
+
id
|
|
119
|
+
name
|
|
120
|
+
email
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
126
|
+
const { loading, error, data, dataState } = useQuery(GET_USER, {
|
|
127
|
+
variables: { id: userId },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (loading) return <p>Loading...</p>;
|
|
131
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
132
|
+
|
|
133
|
+
// TypeScript note: for stricter type narrowing, you can also check `dataState === "complete"` before accessing data
|
|
134
|
+
return <div>{data?.user.name}</div>;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> **Note for TypeScript users**: Use [`dataState`](https://www.apollographql.com/docs/react/data/typescript#type-narrowing-data-with-datastate) for more robust type safety and better type narrowing in Apollo Client 4.x.
|
|
139
|
+
|
|
140
|
+
### TypeScript Integration
|
|
141
|
+
|
|
142
|
+
For complete examples with loading, error handling, and `dataState` for type narrowing, see [Basic Query Usage](#basic-query-usage) above.
|
|
143
|
+
|
|
144
|
+
#### Usage with Generated Types
|
|
145
|
+
|
|
146
|
+
For type-safe operations with code generation, see the [TypeScript Code Generation guide](typescript-codegen.md).
|
|
147
|
+
|
|
148
|
+
Quick example:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { gql } from "@apollo/client";
|
|
152
|
+
import { useQuery } from "@apollo/client/react";
|
|
153
|
+
import { GetUserDocument } from "./queries.generated";
|
|
154
|
+
|
|
155
|
+
// Define your query with the if (false) pattern for code generation
|
|
156
|
+
if (false) {
|
|
157
|
+
gql`
|
|
158
|
+
query GetUser($id: ID!) {
|
|
159
|
+
user(id: $id) {
|
|
160
|
+
id
|
|
161
|
+
name
|
|
162
|
+
email
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
169
|
+
// Types are automatically inferred from GetUserDocument
|
|
170
|
+
const { data } = useQuery(GetUserDocument, {
|
|
171
|
+
variables: { id: userId },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return <div>{data.user.name}</div>;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Usage with Manual Type Annotations
|
|
179
|
+
|
|
180
|
+
If not using code generation, define types alongside your queries using `TypedDocumentNode`:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { gql, TypedDocumentNode } from "@apollo/client";
|
|
184
|
+
import { useQuery } from "@apollo/client/react";
|
|
185
|
+
|
|
186
|
+
interface GetUserData {
|
|
187
|
+
user: {
|
|
188
|
+
id: string;
|
|
189
|
+
name: string;
|
|
190
|
+
email: string;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface GetUserVariables {
|
|
195
|
+
id: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Types should always be defined via TypedDocumentNode alongside your queries/mutations, not at the useQuery/useMutation call site
|
|
199
|
+
const GET_USER: TypedDocumentNode<GetUserData, GetUserVariables> = gql`
|
|
200
|
+
query GetUser($id: ID!) {
|
|
201
|
+
user(id: $id) {
|
|
202
|
+
id
|
|
203
|
+
name
|
|
204
|
+
email
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
210
|
+
const { data } = useQuery(GET_USER, {
|
|
211
|
+
variables: { id: userId },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// data.user is automatically typed from GET_USER
|
|
215
|
+
return <div>{data.user.name}</div>;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Basic Mutation Usage
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { gql, TypedDocumentNode } from "@apollo/client";
|
|
223
|
+
import { useMutation } from "@apollo/client/react";
|
|
224
|
+
|
|
225
|
+
interface CreateUserMutation {
|
|
226
|
+
createUser: {
|
|
227
|
+
id: string;
|
|
228
|
+
name: string;
|
|
229
|
+
email: string;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface CreateUserMutationVariables {
|
|
234
|
+
input: {
|
|
235
|
+
name: string;
|
|
236
|
+
email: string;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const CREATE_USER: TypedDocumentNode<
|
|
241
|
+
CreateUserMutation,
|
|
242
|
+
CreateUserMutationVariables
|
|
243
|
+
> = gql`
|
|
244
|
+
mutation CreateUser($input: CreateUserInput!) {
|
|
245
|
+
createUser(input: $input) {
|
|
246
|
+
id
|
|
247
|
+
name
|
|
248
|
+
email
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
function CreateUserForm() {
|
|
254
|
+
const [createUser, { loading, error }] = useMutation(CREATE_USER);
|
|
255
|
+
|
|
256
|
+
const handleSubmit = async (formData: FormData) => {
|
|
257
|
+
const { data } = await createUser({
|
|
258
|
+
variables: {
|
|
259
|
+
input: {
|
|
260
|
+
name: formData.get("name") as string,
|
|
261
|
+
email: formData.get("email") as string,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
if (data) {
|
|
266
|
+
console.log("Created user:", data.createUser);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<form
|
|
272
|
+
onSubmit={(e) => {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
handleSubmit(new FormData(e.currentTarget));
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
<input name="name" placeholder="Name" />
|
|
278
|
+
<input name="email" placeholder="Email" />
|
|
279
|
+
<button type="submit" disabled={loading}>
|
|
280
|
+
{loading ? "Creating..." : "Create User"}
|
|
281
|
+
</button>
|
|
282
|
+
{error && <p>Error: {error.message}</p>}
|
|
283
|
+
</form>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Client Configuration Options
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const client = new ApolloClient({
|
|
292
|
+
// Required: The cache implementation
|
|
293
|
+
cache: new InMemoryCache({
|
|
294
|
+
typePolicies: {
|
|
295
|
+
Query: {
|
|
296
|
+
fields: {
|
|
297
|
+
// Field-level cache configuration
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
}),
|
|
302
|
+
|
|
303
|
+
// Network layer
|
|
304
|
+
link: new HttpLink({ uri: "/graphql" }),
|
|
305
|
+
|
|
306
|
+
// Avoid defaultOptions if possible as they break TypeScript expectations.
|
|
307
|
+
// Configure options per-query/mutation instead for better type safety.
|
|
308
|
+
// defaultOptions: {
|
|
309
|
+
// watchQuery: { fetchPolicy: 'cache-and-network' },
|
|
310
|
+
// },
|
|
311
|
+
|
|
312
|
+
// DevTools are enabled by default in development
|
|
313
|
+
// Only configure when enabling in production
|
|
314
|
+
devtools: {
|
|
315
|
+
enabled: true, // Only needed for production
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// Custom name for this client instance
|
|
319
|
+
clientAwareness: {
|
|
320
|
+
name: "web-client",
|
|
321
|
+
version: "1.0.0",
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Important Considerations
|
|
327
|
+
|
|
328
|
+
1. **Choose Your Hook Strategy:** Decide if your application should be based on Suspense. If it is, use suspenseful hooks like `useSuspenseQuery` (see [Suspense Hooks guide](suspense-hooks.md)), otherwise use non-suspenseful hooks like `useQuery` (see [Queries guide](queries.md)).
|
|
329
|
+
|
|
330
|
+
2. **Client-Side Only:** This setup is for client-side apps without SSR. The Apollo Client instance is created once and reused throughout the application lifecycle.
|
|
331
|
+
|
|
332
|
+
3. **Authentication:** Prefer HttpOnly cookies with `credentials: "include"` in `HttpLink` options to avoid exposing tokens to JavaScript. If manual token management is necessary, use `SetContextLink` to dynamically add authentication headers from `localStorage` or other client-side storage.
|
|
333
|
+
|
|
334
|
+
4. **Environment Variables:** Store your GraphQL endpoint URL in environment variables for different environments (development, staging, production).
|
|
335
|
+
|
|
336
|
+
5. **Error Handling:** Always handle `loading` and `error` states when using `useQuery` or `useLazyQuery`. For Suspense-based hooks (`useSuspenseQuery`), React handles this through `<Suspense>` boundaries and error boundaries.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Apollo Client Integration with Next.js App Router
|
|
2
|
+
|
|
3
|
+
This guide covers integrating Apollo Client in a Next.js application using the App Router architecture with support for both React Server Components (RSC) and Client Components.
|
|
4
|
+
|
|
5
|
+
## What is supported?
|
|
6
|
+
|
|
7
|
+
### React Server Components
|
|
8
|
+
|
|
9
|
+
Apollo Client provides a shared client instance across all server components for a single request, preventing duplicate GraphQL requests and optimizing server-side rendering.
|
|
10
|
+
|
|
11
|
+
### React Client Components
|
|
12
|
+
|
|
13
|
+
When using the `app` directory, client components are rendered both on the server (SSR) and in the browser. Apollo Client enables you to execute GraphQL queries on the server and use the results to hydrate your browser-side cache, delivering fully-rendered pages to users.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Install Apollo Client and the Next.js integration package:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @apollo/client@latest @apollo/client-integration-nextjs graphql rxjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> **TypeScript users:** For type-safe GraphQL operations, see the [TypeScript Code Generation guide](typescript-codegen.md).
|
|
24
|
+
|
|
25
|
+
## Setup for React Server Components (RSC)
|
|
26
|
+
|
|
27
|
+
### Step 1: Create Apollo Client Configuration
|
|
28
|
+
|
|
29
|
+
Create an `ApolloClient.ts` file in your app directory:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { HttpLink } from "@apollo/client";
|
|
33
|
+
import {
|
|
34
|
+
registerApolloClient,
|
|
35
|
+
ApolloClient,
|
|
36
|
+
InMemoryCache,
|
|
37
|
+
} from "@apollo/client-integration-nextjs";
|
|
38
|
+
|
|
39
|
+
export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
|
|
40
|
+
return new ApolloClient({
|
|
41
|
+
cache: new InMemoryCache(),
|
|
42
|
+
link: new HttpLink({
|
|
43
|
+
// Use an absolute URL for SSR (relative URLs cannot be used in SSR)
|
|
44
|
+
uri: "https://your-api.com/graphql",
|
|
45
|
+
fetchOptions: {
|
|
46
|
+
// Optional: Next.js-specific fetch options for caching and revalidation
|
|
47
|
+
// See: https://nextjs.org/docs/app/api-reference/functions/fetch
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Step 2: Use in Server Components
|
|
55
|
+
|
|
56
|
+
You can now use the `getClient` function or the `query` shortcut in your server components:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { query } from "./ApolloClient";
|
|
60
|
+
|
|
61
|
+
async function UserProfile({ userId }: { userId: string }) {
|
|
62
|
+
const { data } = await query({
|
|
63
|
+
query: GET_USER,
|
|
64
|
+
variables: { id: userId },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return <div>{data.user.name}</div>;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Override Next.js Fetch Options
|
|
72
|
+
|
|
73
|
+
You can override Next.js-specific `fetch` options per query using `context.fetchOptions`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const { data } = await getClient().query({
|
|
77
|
+
query: GET_USER,
|
|
78
|
+
context: {
|
|
79
|
+
fetchOptions: {
|
|
80
|
+
next: { revalidate: 60 }, // Revalidate every 60 seconds
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Setup for Client Components (SSR and Browser)
|
|
87
|
+
|
|
88
|
+
### Step 1: Create Apollo Wrapper Component
|
|
89
|
+
|
|
90
|
+
Create `app/ApolloWrapper.tsx`:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
"use client";
|
|
94
|
+
|
|
95
|
+
import { HttpLink } from "@apollo/client";
|
|
96
|
+
import {
|
|
97
|
+
ApolloNextAppProvider,
|
|
98
|
+
ApolloClient,
|
|
99
|
+
InMemoryCache,
|
|
100
|
+
} from "@apollo/client-integration-nextjs";
|
|
101
|
+
|
|
102
|
+
function makeClient() {
|
|
103
|
+
const httpLink = new HttpLink({
|
|
104
|
+
// Use an absolute URL for SSR
|
|
105
|
+
uri: "https://your-api.com/graphql",
|
|
106
|
+
fetchOptions: {
|
|
107
|
+
// Optional: Next.js-specific fetch options
|
|
108
|
+
// Note: This doesn't work with `export const dynamic = "force-static"`
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return new ApolloClient({
|
|
113
|
+
cache: new InMemoryCache(),
|
|
114
|
+
link: httpLink,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function ApolloWrapper({ children }: React.PropsWithChildren) {
|
|
119
|
+
return (
|
|
120
|
+
<ApolloNextAppProvider makeClient={makeClient}>
|
|
121
|
+
{children}
|
|
122
|
+
</ApolloNextAppProvider>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Step 2: Wrap Root Layout
|
|
128
|
+
|
|
129
|
+
Wrap your `RootLayout` in the `ApolloWrapper` component in `app/layout.tsx`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ApolloWrapper } from "./ApolloWrapper";
|
|
133
|
+
|
|
134
|
+
export default function RootLayout({
|
|
135
|
+
children,
|
|
136
|
+
}: {
|
|
137
|
+
children: React.ReactNode;
|
|
138
|
+
}) {
|
|
139
|
+
return (
|
|
140
|
+
<html lang="en">
|
|
141
|
+
<body>
|
|
142
|
+
<ApolloWrapper>{children}</ApolloWrapper>
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> **Note:** This works even if your layout is a React Server Component. It ensures all Client Components share the same Apollo Client instance through `ApolloNextAppProvider`.
|
|
150
|
+
|
|
151
|
+
### Step 3: Use Apollo Client Hooks in Client Components
|
|
152
|
+
|
|
153
|
+
For optimal streaming SSR, use suspense-enabled hooks like `useSuspenseQuery` and `useFragment`:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
"use client";
|
|
157
|
+
|
|
158
|
+
import { useSuspenseQuery } from "@apollo/client/react";
|
|
159
|
+
|
|
160
|
+
export function UserProfile({ userId }: { userId: string }) {
|
|
161
|
+
const { data } = useSuspenseQuery(GET_USER, {
|
|
162
|
+
variables: { id: userId },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return <div>{data.user.name}</div>;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Preloading Data from RSC to Client Components
|
|
170
|
+
|
|
171
|
+
You can preload data in React Server Components to populate the cache of your Client Components.
|
|
172
|
+
|
|
173
|
+
### Step 1: Use PreloadQuery in Server Components
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { PreloadQuery } from "./ApolloClient";
|
|
177
|
+
import { Suspense } from "react";
|
|
178
|
+
|
|
179
|
+
export default async function Page() {
|
|
180
|
+
return (
|
|
181
|
+
<PreloadQuery query={GET_USER} variables={{ id: "1" }}>
|
|
182
|
+
<Suspense fallback={<>Loading...</>}>
|
|
183
|
+
<ClientChild />
|
|
184
|
+
</Suspense>
|
|
185
|
+
</PreloadQuery>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Step 2: Consume with useSuspenseQuery in Client Components
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
"use client";
|
|
194
|
+
|
|
195
|
+
import { useSuspenseQuery } from "@apollo/client/react";
|
|
196
|
+
|
|
197
|
+
export function ClientChild() {
|
|
198
|
+
const { data } = useSuspenseQuery(GET_USER, {
|
|
199
|
+
variables: { id: "1" },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return <div>{data.user.name}</div>;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
> **Important:** Data fetched this way should be considered client data and never referenced in Server Components. `PreloadQuery` prevents mixing server data and client data by creating a separate `ApolloClient` instance.
|
|
207
|
+
|
|
208
|
+
### Using with useReadQuery
|
|
209
|
+
|
|
210
|
+
For advanced use cases, you can use `PreloadQuery` with `useReadQuery` to avoid request waterfalls:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<PreloadQuery query={GET_USER} variables={{ id: "1" }}>
|
|
214
|
+
{(queryRef) => (
|
|
215
|
+
<Suspense fallback={<>Loading...</>}>
|
|
216
|
+
<ClientChild queryRef={queryRef} />
|
|
217
|
+
</Suspense>
|
|
218
|
+
)}
|
|
219
|
+
</PreloadQuery>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
In your Client Component:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
"use client";
|
|
226
|
+
|
|
227
|
+
import {
|
|
228
|
+
useQueryRefHandlers,
|
|
229
|
+
useReadQuery,
|
|
230
|
+
QueryRef,
|
|
231
|
+
} from "@apollo/client/react";
|
|
232
|
+
|
|
233
|
+
export function ClientChild({ queryRef }: { queryRef: QueryRef<TQueryData> }) {
|
|
234
|
+
const { refetch } = useQueryRefHandlers(queryRef);
|
|
235
|
+
const { data } = useReadQuery(queryRef);
|
|
236
|
+
|
|
237
|
+
return <div>{data.user.name}</div>;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Handling Multipart Responses (@defer) in SSR
|
|
242
|
+
|
|
243
|
+
When using the `@defer` directive, `useSuspenseQuery` will only suspend until the initial response is received. To handle deferred data properly, you have three strategies:
|
|
244
|
+
|
|
245
|
+
### Strategy 1: Use PreloadQuery with useReadQuery
|
|
246
|
+
|
|
247
|
+
`PreloadQuery` allows deferred data to be fully transported and streamed chunk-by-chunk.
|
|
248
|
+
|
|
249
|
+
### Strategy 2: Remove @defer Fragments
|
|
250
|
+
|
|
251
|
+
Use `RemoveMultipartDirectivesLink` to strip `@defer` directives from queries during SSR:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { RemoveMultipartDirectivesLink } from "@apollo/client-integration-nextjs";
|
|
255
|
+
|
|
256
|
+
new RemoveMultipartDirectivesLink({
|
|
257
|
+
stripDefer: true, // Default: true
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
You can exclude specific fragments from stripping by labeling them:
|
|
262
|
+
|
|
263
|
+
```graphql
|
|
264
|
+
query myQuery {
|
|
265
|
+
fastField
|
|
266
|
+
... @defer(label: "SsrDontStrip1") {
|
|
267
|
+
slowField1
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Strategy 3: Wait for Deferred Data
|
|
273
|
+
|
|
274
|
+
Use `AccumulateMultipartResponsesLink` to debounce the initial response:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { AccumulateMultipartResponsesLink } from "@apollo/client-integration-nextjs";
|
|
278
|
+
|
|
279
|
+
new AccumulateMultipartResponsesLink({
|
|
280
|
+
cutoffDelay: 100, // Wait up to 100ms for incremental data
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Combined Approach: SSRMultipartLink
|
|
285
|
+
|
|
286
|
+
Combine both strategies with `SSRMultipartLink`:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { SSRMultipartLink } from "@apollo/client-integration-nextjs";
|
|
290
|
+
|
|
291
|
+
new SSRMultipartLink({
|
|
292
|
+
stripDefer: true,
|
|
293
|
+
cutoffDelay: 100,
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Testing
|
|
298
|
+
|
|
299
|
+
Reset singleton instances between tests using the `resetApolloClientSingletons` helper:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { resetApolloClientSingletons } from "@apollo/client-integration-nextjs";
|
|
303
|
+
|
|
304
|
+
afterEach(resetApolloClientSingletons);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Debugging
|
|
308
|
+
|
|
309
|
+
Enable verbose logging in your `app/ApolloWrapper.tsx`:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { setLogVerbosity } from "@apollo/client";
|
|
313
|
+
|
|
314
|
+
setLogVerbosity("debug");
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Important Considerations
|
|
318
|
+
|
|
319
|
+
1. **Separate RSC and SSR Queries:** Avoid overlapping queries between RSC and SSR. RSC queries don't update in the browser, while SSR queries can update dynamically as the cache changes.
|
|
320
|
+
|
|
321
|
+
2. **Use Absolute URLs:** Always use absolute URLs in `HttpLink` for SSR, as relative URLs cannot be used in server-side rendering.
|
|
322
|
+
|
|
323
|
+
3. **Streaming SSR:** For optimal performance, use `useSuspenseQuery` and `useFragment` to take advantage of React 18's streaming SSR capabilities.
|
|
324
|
+
|
|
325
|
+
4. **Suspense Boundaries:** Place `Suspense` boundaries at meaningful places in your UI for the best user experience.
|