Skip to content

Commit

Permalink
docs: Update nextjs demo and installation guides
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Jun 6, 2024
1 parent f405566 commit f321a20
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 186 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-frogs-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@data-client/ssr': patch
---

Update README to include DataProvider (app routes) docs
109 changes: 84 additions & 25 deletions docs/core/guides/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,88 @@ load and slow client hydration, potentially causing application stutters.

## NextJS SSR {#nextjs}

We've optimized integration into NextJS with a custom [Document](https://nextjs.org/docs/advanced-features/custom-document)
### App Router

NextJS 14 includes a new way of routing in the '/app' directory. This allows further
performance improvements, as well as dynamic and nested routing.

#### Root Layout

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)

```tsx title="app/layout.tsx"
import { DataProvider } from '@data-client/ssr/nextjs';
import { AsyncBoundary } from '@data-client/react';

export default function RootLayout({ children }) {
return (
<html>
<body>
<DataProvider>
<header>Title</header>
<AsyncBoundary>{children}</AsyncBoundary>
<footer></footer>
</DataProvider>
</body>
</html>
);
}
```

#### Client Components

To keep your data fresh and performant, you can use client components and [useSuspense()](../api/useSuspense.md)

```tsx title="app/todos/page.tsx"
'use client';
import { useSuspense } from '@data-client/react';
import { TodoResource } from '../../resources/Todo';

export default function InteractivePage() {
const todos = useSuspense(TodoResource.getList);
return <TodoList todos={todos} />;
}
```

#### Server Components

However, if your data never changes, you can slightly decrease the javascript bundle sent, by
using a server component. Simply `await` the endpoint:

```tsx title="app/todos/page.tsx"
import { TodoResource } from '../../resources/Todo';

export default async function StaticPage() {
const todos = await TodoResource.getList();
return <TodoList todos={todos} />;
}
```

#### Demo

<StackBlitz app="nextjs" file="components/todo/TodoList.tsx,app/layout.tsx" view="both" />

#### Class mangling and Entity.key

NextJS will rename classes for production builds. Due to this, it's critical to
define [Entity.key](/rest/api/Entity#key) as its default implementation is based on
the class name.

```ts
class User extends Entity {
id = '';
username = '';

pk() { return this.id }

// highlight-next-line
static key = 'User';
}
```

### Pages Router

With NextJS &lt; 14, you might be using the pages router. For this we have [Document](https://nextjs.org/docs/advanced-features/custom-document)
and NextJS specific wrapper for [App](https://nextjs.org/docs/advanced-features/custom-app)

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

:::

### Demo

<StackBlitz app="nextjs" file="components/todo/TodoList.tsx,pages/_app.tsx,pages/_document.tsx" view="both" />

### Further customizing Document
#### Further customizing Document

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

Expand Down Expand Up @@ -107,7 +184,7 @@ export default class MyDocument extends DataClientDocument {
}
```

### CSP Nonce
#### CSP Nonce

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

### Class mangling and Entity.key

NextJS will rename classes for production builds. Due to this, it's critical to
define [Entity.key](/rest/api/Entity#key) as its default implementation is based on
the class name.

```ts
class User extends Entity {
id = '';
username = '';

pk() { return this.id }

// highlight-next-line
static key = 'User';
}
```

## Express JS SSR

When implementing your own server using express.
Expand Down
30 changes: 15 additions & 15 deletions docs/core/shared/_installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import Link from '@docusaurus/Link';
defaultValue="18-web"
groupId="platform"
values={[
{ label: 'React Web 16+', value: 'web' },
{ label: 'React Web 18+', value: '18-web' },
{ label: 'React Native', value: 'native' },
{ label: 'NextJS', value: 'nextjs' },
{ label: 'NextJS 14+', value: 'nextjs' },
{ label: 'Anansi', value: 'anansi' },
{ label: 'React Web 16+', value: 'web' },
]}>
<TabItem value="web">

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

```tsx title="pages/_document.tsx"
import { DataClientDocument } from '@data-client/ssr/nextjs';

export default DataClientDocument;
```

```tsx title="pages/_app.tsx"
import { AppCacheProvider } from '@data-client/ssr/nextjs';
import type { AppProps } from 'next/app';
```tsx title="app/layout.tsx"
import { DataProvider } from '@data-client/ssr/nextjs';
import { AsyncBoundary } from '@data-client/react';

export default function App({ Component, pageProps }: AppProps) {
export default function RootLayout({ children }) {
return (
<AppCacheProvider>
<Component {...pageProps} />
</AppCacheProvider>
<html>
<body>
<DataProvider>
<header>Title</header>
<AsyncBoundary>{children}</AsyncBoundary>
<footer></footer>
</DataProvider>
</body>
</html>
);
}
```
Expand Down
3 changes: 0 additions & 3 deletions examples/nextjs/.babelrc

This file was deleted.

40 changes: 40 additions & 0 deletions examples/nextjs/app/[userId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Link from 'next/link';
import UserSelection from '../../components/todo/UserSelection';

export default function TodoLayout({
children,
params,
}: {
children: React.ReactNode;
params?: { userId: number };
}) {
return (
<>
<title>NextJS + Reactive Data Client = ❤️</title>
<meta
name="description"
content="NextJS integration with Reactive Data Client"
/>

<UserSelection userId={params?.userId} />

{children}

<p>
No fetch requests took place on the client. The client is immediately
interactive without the need for revalidation.
</p>

<p>
This is because Reactive Data Client's store is initialized and{' '}
<a href="https://dataclient.io/docs/concepts/normalization">
normalized
</a>
</p>

<p>
<Link href="/crypto">Live BTC Price</Link>
</p>
</>
);
}
8 changes: 8 additions & 0 deletions examples/nextjs/app/[userId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client';
import TodoList from '../../components/todo/TodoList';

export default function TodoPage({ params }: { params: { userId: number } }) {
return (
<TodoList {...params} />
);
}
34 changes: 34 additions & 0 deletions examples/nextjs/app/crypto/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';
import Link from 'next/link';

import AssetPrice from '../../components/AssetPrice';
import styles from '../../styles/Home.module.css';

export default function Crypto() {
return (
<>
<title>Live Crypto Prices with Reactive Data Client</title>
<meta
name="description"
content="Live BTC price using the Reactive Data Client"
/>

<h2 className={styles.subtitle}>
Here we show the live price of BTC using Reactive Data Client
</h2>

<p className={styles.price}>
<AssetPrice symbol="BTC" />
</p>

<p>
The latest price is immediately available before any JavaScript runs;
while automatically updating as prices change.
</p>

<p>
<Link href="/">Todo List</Link>
</p>
</>
);
}
47 changes: 47 additions & 0 deletions examples/nextjs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AsyncBoundary } from '@data-client/react';
import { DataProvider } from '@data-client/ssr/nextjs';
import Image from 'next/image';

import styles from '../styles/Home.module.css';

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<DataProvider>
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a> with{' '}
<a href="https://dataclient.io">Reactive Data Client</a>
</h1>

<AsyncBoundary>{children}</AsyncBoundary>
</main>

<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image
src="/vercel.svg"
alt="Vercel Logo"
width={72}
height={16}
/>
</span>
</a>
</footer>
</div>
</DataProvider>
</body>
</html>
);
}
10 changes: 10 additions & 0 deletions examples/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import TodoLayout from './[userId]/layout';
import TodoPage from './[userId]/page';

export default function Home() {
return (
<TodoLayout params={{ userId: 1 }}>
<TodoPage params={{ userId: 1 }} />
</TodoLayout>
);
}
18 changes: 18 additions & 0 deletions examples/nextjs/components/todo/UserSelect.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.userLink {
display: inline-block;
text-decoration: none;
margin: 0 7px;
color: blue;

}
.userLink.active {
color: red;
}
.userLink:hover {
text-decoration: underline;
}

.wrap {
margin-top: 20px;
text-align: center;
}
25 changes: 25 additions & 0 deletions examples/nextjs/components/todo/UserSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';
import { useSuspense } from '@data-client/react';
import { UserResource } from '../../resources/UserResource';
import Link from 'next/link';
import styles from './UserSelect.module.css';
import clsx from 'clsx';

export default function UserSelection({ userId }: { userId?: number }) {
const users = useSuspense(UserResource.getList).slice(0, 7);
return (
<div className={styles.wrap}>
{users.map(user => (
<Link
key={user.pk()}
href={`/${user.id === 1 ? '' : user.id}`}
className={clsx(styles.userLink, {
[styles.active]: user.id == userId,
})}
>
{user.name}
</Link>
))}
</div>
);
}
11 changes: 0 additions & 11 deletions examples/nextjs/pages/_app.tsx

This file was deleted.

Loading

0 comments on commit f321a20

Please sign in to comment.