Skip to content

Commit 8493e50

Browse files
committed
docs: Update nextjs demo and installation guides
1 parent f405566 commit 8493e50

File tree

16 files changed

+340
-183
lines changed

16 files changed

+340
-183
lines changed

.changeset/afraid-frogs-sell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@data-client/ssr': patch
3+
---
4+
5+
Update README to include DataProvider (app routes) docs

docs/core/guides/ssr.md

+84-25
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,88 @@ load and slow client hydration, potentially causing application stutters.
1818

1919
## NextJS SSR {#nextjs}
2020

21-
We've optimized integration into NextJS with a custom [Document](https://nextjs.org/docs/advanced-features/custom-document)
21+
### App Router
22+
23+
NextJS 14 includes a new way of routing in the '/app' directory. This allows further
24+
performance improvements, as well as dynamic and nested routing.
25+
26+
#### Root Layout
27+
28+
Place [DataProvider](https://dataclient.io/docs/api/DataProvider) in your [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required)
29+
30+
```tsx title="app/layout.tsx"
31+
import { DataProvider } from '@data-client/ssr/nextjs';
32+
import { AsyncBoundary } from '@data-client/react';
33+
34+
export default function RootLayout({ children }) {
35+
return (
36+
<html>
37+
<body>
38+
<DataProvider>
39+
<header>Title</header>
40+
<AsyncBoundary>{children}</AsyncBoundary>
41+
<footer></footer>
42+
</DataProvider>
43+
</body>
44+
</html>
45+
);
46+
}
47+
```
48+
49+
#### Client Components
50+
51+
To keep your data fresh and performant, you can use client components and [useSuspense()](../api/useSuspense.md)
52+
53+
```tsx title="app/todos/page.tsx"
54+
'use client';
55+
import { useSuspense } from '@data-client/react';
56+
import { TodoResource } from '../../resources/Todo';
57+
58+
export default function InteractivePage() {
59+
const todos = useSuspense(TodoResource.getList);
60+
return <TodoList todos={todos} />;
61+
}
62+
```
63+
64+
#### Server Components
65+
66+
However, if your data never changes, you can slightly decrease the javascript bundle sent, by
67+
using a server component. Simply `await` the endpoint:
68+
69+
```tsx title="app/todos/page.tsx"
70+
import { TodoResource } from '../../resources/Todo';
71+
72+
export default async function StaticPage() {
73+
const todos = await TodoResource.getList();
74+
return <TodoList todos={todos} />;
75+
}
76+
```
77+
78+
#### Demo
79+
80+
<StackBlitz app="nextjs" file="components/todo/TodoList.tsx,app/layout.tsx" view="both" />
81+
82+
#### Class mangling and Entity.key
83+
84+
NextJS will rename classes for production builds. Due to this, it's critical to
85+
define [Entity.key](/rest/api/Entity#key) as its default implementation is based on
86+
the class name.
87+
88+
```ts
89+
class User extends Entity {
90+
id = '';
91+
username = '';
92+
93+
pk() { return this.id }
94+
95+
// highlight-next-line
96+
static key = 'User';
97+
}
98+
```
99+
100+
### Pages Router
101+
102+
With NextJS &lt; 14, you might be using the pages router. For this we have [Document](https://nextjs.org/docs/advanced-features/custom-document)
22103
and NextJS specific wrapper for [App](https://nextjs.org/docs/advanced-features/custom-app)
23104

24105
<PkgTabs pkgs="@data-client/ssr @data-client/redux redux" />
@@ -59,11 +140,7 @@ export const getServerSideProps = () => ({ props: {} });
59140

60141
:::
61142

62-
### Demo
63-
64-
<StackBlitz app="nextjs" file="components/todo/TodoList.tsx,pages/_app.tsx,pages/_document.tsx" view="both" />
65-
66-
### Further customizing Document
143+
#### Further customizing Document
67144

68145
To further customize Document, simply extend from the provided document.
69146

@@ -107,7 +184,7 @@ export default class MyDocument extends DataClientDocument {
107184
}
108185
```
109186

110-
### CSP Nonce
187+
#### CSP Nonce
111188

112189
Reactive Data Client Document serializes the store state in a script tag. In case you have
113190
Content Security Policy restrictions that require use of a nonce, you can override
@@ -129,24 +206,6 @@ export default class MyDocument extends DataClientDocument {
129206
}
130207
```
131208

132-
### Class mangling and Entity.key
133-
134-
NextJS will rename classes for production builds. Due to this, it's critical to
135-
define [Entity.key](/rest/api/Entity#key) as its default implementation is based on
136-
the class name.
137-
138-
```ts
139-
class User extends Entity {
140-
id = '';
141-
username = '';
142-
143-
pk() { return this.id }
144-
145-
// highlight-next-line
146-
static key = 'User';
147-
}
148-
```
149-
150209
## Express JS SSR
151210

152211
When implementing your own server using express.

docs/core/shared/_installation.mdx

+17-15
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import Link from '@docusaurus/Link';
88
defaultValue="18-web"
99
groupId="platform"
1010
values={[
11-
{ label: 'React Web 16+', value: 'web' },
1211
{ label: 'React Web 18+', value: '18-web' },
1312
{ label: 'React Native', value: 'native' },
14-
{ label: 'NextJS', value: 'nextjs' },
13+
{ label: 'NextJS 14+', value: 'nextjs' },
1514
{ label: 'Anansi', value: 'anansi' },
15+
{ label: 'React Web 16+', value: 'web' },
1616
]}>
1717
<TabItem value="web">
1818

@@ -73,21 +73,23 @@ Alternatively [integrate state with redux](../guides/redux.md)
7373
<Link className="button button--primary" to="../guides/ssr#nextjs">Full NextJS Guide</Link>
7474
</p>
7575

76-
```tsx title="pages/_document.tsx"
77-
import { DataClientDocument } from '@data-client/ssr/nextjs';
78-
79-
export default DataClientDocument;
80-
```
81-
82-
```tsx title="pages/_app.tsx"
83-
import { AppCacheProvider } from '@data-client/ssr/nextjs';
84-
import type { AppProps } from 'next/app';
76+
```tsx title="app/layout.tsx"
77+
import { DataProvider } from '@data-client/ssr/nextjs';
78+
import { AsyncBoundary } from '@data-client/react';
8579

86-
export default function App({ Component, pageProps }: AppProps) {
80+
export default function RootLayout({ children }) {
8781
return (
88-
<AppCacheProvider>
89-
<Component {...pageProps} />
90-
</AppCacheProvider>
82+
<html>
83+
<body>
84+
// highlight-next-line
85+
<DataProvider>
86+
<header>Title</header>
87+
<AsyncBoundary>{children}</AsyncBoundary>
88+
<footer></footer>
89+
// highlight-next-line
90+
</DataProvider>
91+
</body>
92+
</html>
9193
);
9294
}
9395
```
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Link from 'next/link';
2+
import UserSelection from '../../components/todo/UserSelection';
3+
4+
export default function TodoLayout({
5+
children,
6+
params,
7+
}: {
8+
children: React.ReactNode;
9+
params?: { userId: number };
10+
}) {
11+
return (
12+
<>
13+
<title>NextJS + Reactive Data Client = ❤️</title>
14+
<meta
15+
name="description"
16+
content="NextJS integration with Reactive Data Client"
17+
/>
18+
19+
<UserSelection userId={params?.userId} />
20+
21+
{children}
22+
23+
<p>
24+
No fetch requests took place on the client. The client is immediately
25+
interactive without the need for revalidation.
26+
</p>
27+
28+
<p>
29+
This is because Reactive Data Client's store is initialized and{' '}
30+
<a href="https://dataclient.io/docs/concepts/normalization">
31+
normalized
32+
</a>
33+
</p>
34+
35+
<p>
36+
<Link href="/crypto">Live BTC Price</Link>
37+
</p>
38+
</>
39+
);
40+
}

examples/nextjs/app/[userId]/page.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client';
2+
import TodoList from '../../components/todo/TodoList';
3+
4+
export default function TodoPage({ params }: { params: { userId: number } }) {
5+
return (
6+
<TodoList {...params} />
7+
);
8+
}

examples/nextjs/app/crypto/page.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use client';
2+
import Link from 'next/link';
3+
4+
import AssetPrice from '../../components/AssetPrice';
5+
import styles from '../../styles/Home.module.css';
6+
7+
export default function Crypto() {
8+
return (
9+
<>
10+
<title>Live Crypto Prices with Reactive Data Client</title>
11+
<meta
12+
name="description"
13+
content="Live BTC price using the Reactive Data Client"
14+
/>
15+
16+
<h2 className={styles.subtitle}>
17+
Here we show the live price of BTC using Reactive Data Client
18+
</h2>
19+
20+
<p className={styles.price}>
21+
<AssetPrice symbol="BTC" />
22+
</p>
23+
24+
<p>
25+
The latest price is immediately available before any JavaScript runs;
26+
while automatically updating as prices change.
27+
</p>
28+
29+
<p>
30+
<Link href="/">Todo List</Link>
31+
</p>
32+
</>
33+
);
34+
}

examples/nextjs/app/layout.tsx

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { AsyncBoundary } from '@data-client/react';
2+
import { DataProvider } from '@data-client/ssr/nextjs';
3+
import Image from 'next/image';
4+
5+
import styles from '../styles/Home.module.css';
6+
7+
export default function Layout({ children }: { children: React.ReactNode }) {
8+
return (
9+
<html lang="en">
10+
<head>
11+
<link rel="icon" href="/favicon.ico" />
12+
</head>
13+
<body>
14+
<DataProvider>
15+
<div className={styles.container}>
16+
<main className={styles.main}>
17+
<h1 className={styles.title}>
18+
Welcome to <a href="https://nextjs.org">Next.js!</a> with{' '}
19+
<a href="https://dataclient.io">Reactive Data Client</a>
20+
</h1>
21+
22+
<AsyncBoundary>{children}</AsyncBoundary>
23+
</main>
24+
25+
<footer className={styles.footer}>
26+
<a
27+
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
28+
target="_blank"
29+
rel="noopener noreferrer"
30+
>
31+
Powered by{' '}
32+
<span className={styles.logo}>
33+
<Image
34+
src="/vercel.svg"
35+
alt="Vercel Logo"
36+
width={72}
37+
height={16}
38+
/>
39+
</span>
40+
</a>
41+
</footer>
42+
</div>
43+
</DataProvider>
44+
</body>
45+
</html>
46+
);
47+
}

examples/nextjs/app/page.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import TodoLayout from './[userId]/layout';
2+
import TodoPage from './[userId]/page';
3+
4+
export default function Home() {
5+
return (
6+
<TodoLayout params={{ userId: 1 }}>
7+
<TodoPage params={{ userId: 1 }} />
8+
</TodoLayout>
9+
);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.userLink {
2+
display: inline-block;
3+
text-decoration: none;
4+
margin: 0 7px;
5+
color: blue;
6+
7+
}
8+
.userLink.active {
9+
color: red;
10+
}
11+
.userLink:hover {
12+
text-decoration: underline;
13+
}
14+
15+
.wrap {
16+
margin-top: 20px;
17+
text-align: center;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client';
2+
import { useSuspense } from '@data-client/react';
3+
import { UserResource } from '../../resources/UserResource';
4+
import Link from 'next/link';
5+
import styles from './UserSelect.module.css';
6+
import clsx from 'clsx';
7+
8+
export default function UserSelection({ userId }: { userId?: number }) {
9+
const users = useSuspense(UserResource.getList).slice(0, 7);
10+
return (
11+
<div className={styles.wrap}>
12+
{users.map(user => (
13+
<Link
14+
key={user.pk()}
15+
href={`/${user.id === 1 ? '' : user.id}`}
16+
className={clsx(styles.userLink, {
17+
[styles.active]: user.id == userId,
18+
})}
19+
>
20+
{user.name}
21+
</Link>
22+
))}
23+
</div>
24+
);
25+
}

examples/nextjs/pages/_app.tsx

-11
This file was deleted.

0 commit comments

Comments
 (0)