Next.js と Squid
意見に左右されないプラットフォームとして、Squid はお気に入りのフロントエンド/フルスタックフレームワークと併用できます。Next.js を使用している場合、Squid をプロジェクトに追加することで、データソースへの接続の合理化、追加のセキュリティの強化、リアルタイムでのデータ更新のサポートなど、さまざまな機能を実現できます!さらに、Next.js 開発者をよりサポートするために、Squid と Next.js での開発をシームレスな体験にするいくつかのフックも追加しました。
TL;DR
このチュートリアルでは、Squid を Next.js アプリに統合する方法を学びます。以下のことを含みます:
- クライアントへのリアルタイムでのデータクエリおよびストリーミング更新
- ページ読み込み時に Next.js サーバからデータをクエリする方法
- クライアントとサーバの両方からデータをミューテートする方法
このドキュメントは、Pages Router または App Router のどちらを使用しているかによって若干異なりますので、下記の正しいオプションを必ず選択してください。
Create a new Next.js app
next-tutorial-project
という名前のルートプロジェクトディレクトリを作成します:
mkdir next-tutorial-project
next-tutorial-project
ディレクトリに移動し、次のコマンドのいずれかを実行して Next.js アプリを作成します:
For App Router:
cd next-tutorial-project
npx create-next-app@latest next-tutorial --ts --tailwind --eslint --app --no-src-dir
For Pages Router:
cd next-tutorial-project
npx create-next-app@latest next-tutorial --ts --tailwind --eslint --no-src-dir
プロジェクトについての注意事項:
- プロジェクトは Typescript を使用していますが、あなた自身のプロジェクトでは JavaScript を使用することも可能です。
- Pages Router と App Router の両方が Squid と統合されています。このチュートリアルでは、お好みの方法を選んでください。
- プロジェクトは Tailwind CSS でスタイリングされています。
プロジェクトの作成が完了したら、next-tutorial
ディレクトリに移動し、Squid React SDK をインストールします。Squid React SDK は、Squid を React および Next.js プロジェクトに統合するためのフックやユーティリティを提供します。
cd next-tutorial
npm install @squidcloud/react
Create the Squid backend
- Squid Console に移動し、
next-tutorial
という新しいアプリケーションを作成します。
Squid は dev
(開発用)と prod
(本番用)の2つのターゲット環境を提供します。このチュートリアルでは、開発用に設計された dev
環境を使用します。アプリケーションが正常に動作するために、プロジェクト全体で dev
環境を使用していることを確認してください。詳細については、Squid の environments をお読みください。
-
Squid Console のアプリケーション概要ページに移動し、Backend project セクションまでスクロールします。Initialize backend をクリックし、初期化コマンドをコピーします。
-
ルートプロジェクトディレクトリに移動します:
cd ..
- コンソールからコピーしたコマンドを使用して、バックエンドを初期化します。コマンドの形式は以下の通りです:
squid init next-tutorial-backend --appId [YOUR_APP_ID] --apiKey [YOUR_API_KEY] --environmentId dev --squidDeveloperId [YOUR_SQUID_DEVELOPER_ID] --region [YOUR_REGION (likely us-east-1.aws)]
Run the project
Squid プロジェクトをローカルで実行するには、クライアントアプリとバックエンドの Squid プロジェクトの両方をローカルで実行する必要があります。
next-tutorial-backend
ディレクトリに移動し、squid start
を使用してバックエンドを起動します:
cd next-tutorial-backend
squid start
- 新しいターミナルウィンドウを開き、
next-tutorial
ディレクトリに移動してから、アプリを実行します:
cd next-tutorial
npm run dev
Next.js アプリプロジェクトは現在、ターミナルにログとして出力された PORT
の http://localhost:PORT で実行されています。まだページに表示する内容を編集していないため、表示されるのは Next.js のスタータープロジェクトです。
Router
ここで、このチュートリアルは Next.js の App Router を使用しているか Pages Router を使用しているかによって分岐します。Next.js プロジェクト作成時に選んだオプションを必ず選択してください。
- App Router
- Pages Router
アプリ
Squid を Next.js と一緒に使用する場合、アプリケーションのサーバーサイドとクライアントサイドの両方で Squid client にアクセスできます。サーバー側では、初期ペイロードの一部としてデータをクエリすることが可能です。クライアント側では、クエリやミューテーション、そしてリアルタイムのデータ更新のストリーミングを実行できます。
App Router を使用すると、use client
と use server
ディレクティブを使用して、クライアントでレンダリングされるコンポーネントとサーバーでレンダリングされるコンポーネントを区別することができます。React Server Components (RSCs) は、レンダリング前に非同期処理(例えばデータのクエリなど)を実行できるという点でユニークです。本チュートリアルでは、まずクライアントで Squid を使用し、その後 React Server Components を使用する方法に移行します。
まず、app/layout.tsx
内で、children
を SquidContextProvider
でラップします。プレースホルダーを Squid の設定オプションに置き換えてください。これらの値は Squid Console または .env
ファイル内にあります。.env
ファイルは creating the Squid backend の際に自動生成され、backend ディレクトリに配置されています。
import './globals.css';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { SquidContextProvider } from '@squidcloud/react';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<SquidContextProvider
options={{
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
}}
>
{children}
</SquidContextProvider>
</body>
</html>
);
}
ユーザーのクエリ
次に、ユーザーの一覧を表示する新しいコンポーネントを作成します。app
ディレクトリの下に components
ディレクトリを作成し、users.tsx
という新しいファイルを作成してください。新しいファイルに以下のコードを追加します:
'use client';
import { useCollection, useQuery } from '@squidcloud/react';
type User = {
id: string;
};
export default function Users() {
const collection = useCollection<User>('users');
const { loading, data, error } = useQuery(collection.query().dereference());
if (loading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
Loading...
</div>
);
}
if (error) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
{error.message}
</div>
);
}
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
Users
コンポーネントをレンダリングするために、app/page.tsx
内のすべてのコードを以下の内容に置き換えてください:
import Users from '@/components/users';
export default function Home() {
return <Users />;
}
Squid はアウト・オブ・ザ・ボックスで組み込みのデータベースを提供します。この例では、Users
クライアントコンポーネント内で useQuery
フックを使用し、データベースの users
コレクションに対するクエリを実行しています。このフックはクエリと、テーブルへのライブアップデートを購読するかどうかを示すブール値を受け取ります。query().dereference()
を使用すると、クエリの生データが返ってきます。
Web アプリでは、「Users」という見出しは表示されますが、ユーザーの情報が表示されません!まだコレクションにユーザーを挿入する必要があります。
もし「Loading…」やエラーメッセージが表示される場合は、Squid backend を起動しているか確認してください。tutorial-backend
に移動して squid start
を実行します。詳細は Running the project を参照してください。
ユーザーの挿入
データベースにユーザーを挿入する関数をトリガーするボタンコンポーネントを追加します。components/users.tsx
に以下のコードを追加してください:
import { useCollection, useQuery } from "@squidcloud/react";
...
export default function Users() {
...
const insertUser = async () => {
await collection.doc().insert({
id: crypto.randomUUID(),
});
}
...
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<button onClick={insertUser}>Insert</button>
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
これで Web アプリには Insert ボタンが表示されます。ボタンをクリックすると、ランダムな ID を持つユーザーが挿入されます。データベースのクエリはライブアップデートを購読しているため、ボタンをクリックするとすぐにユーザーの ID がユーザー一覧に表示されます。collection.doc().insert(...)
を実行することで、ユーザーデータがアプリケーションの組み込みデータベースに保存され、ページをリロードしてもユーザー一覧が保持されます。
サーバーでのクエリ実行
ページをリロードすると、ユーザー一覧が表示される前に一瞬 Loading…* インジケーターが表示されます。これは、Users
コンポーネントがクライアントコンポーネントであり、クライアント側でユーザーのクエリを実行するのに短時間かかるためです。Next.js の App Router を使用すると、このクエリを React Server Component 内で実行し、ページロード時にそのデータをサーバーからクライアントに渡すことができます。
app/page.tsx
では、Squid クエリを直接実行して初期のユーザーデータを取得し、その users
のリストを Users
コンポーネントの初期レンダリングに渡します:
import Users from '@/components/users';
import { Squid } from '@squidcloud/client';
type User = {
id: string;
};
export default async function Home() {
const squid = Squid.getInstance({
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
});
const users = await squid
.collection<User>('users')
.query()
.dereference()
.snapshot();
return <Users users={users} />;
}
最初にアプリをセットアップする際、SquidContextProvider
を使用してクライアント側で Squid を初期化しました。この Squid のインスタンスは、React Server Components では React Context にアクセスできないため、Home
ページ内では利用できません。代わりに、Squid React SDK をインストールすると自動的にインストールされる @squidcloud/client
パッケージを使用して、別のインスタンスを作成する必要があります。コードの重複を減らすために、Squid の設定オプションを取得するための共有ユーティリティを作成することを推奨します。
utils
フォルダを作成し、squid.ts
というファイルを追加します。新しいファイルに以下のコードを追加してください:
import { SquidOptions } from '@squidcloud/client';
export function getOptions(): SquidOptions {
return {
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
};
}
その後、どこかで options={...}
を使用している箇所を options={getOptions()}
に置き換えることができます。
app/layout.tsx
では、getOptions
関数をインポートし、SquidContextProvider
の options
として渡してください:
import { getOptions } from "@/utils/squid";
...
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<SquidContextProvider options={getOptions()}>
{children}
</SquidContextProvider>
</body>
</html>
);
}
サーバー上でクエリされた users
にアクセスできるようになったので、Users
コンポーネント内の useQuery
フックを初期値を受け取れるように更新します:
...
export default function Users({ users }: { user: Array<User> }) {
...
const { loading, data, error } = useQuery(
collection.query().dereference(),
{ initialData: users }
);
...
if (loading && !data.length) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
Loading...
</div>
);
}
...
}
このコードブロックで行っている変更は以下の通りです:
users
のリストをuseQuery
フックの初期データとして渡すことで、フックから返される初期のdata
が空の配列ではなくユーザー一覧になるようにします。- Loading… の条件を、データが存在するかどうかで判定するように更新しています。デフォルトでは、初期値を
useQuery
に渡していても、クライアント側でデータが正常にクエリされるまでloading
の値はtrue
になります。データの存在をチェックすることで、クライアント側でクエリ中でもサーバーから得られたユーザー一覧をレンダリングできます。
これらの変更により、ページをリロードしても Loading… インジケーターは表示されず、ページロード時にユーザー一覧がすぐに表示されます。
withServerQuery を使って重複を最小限に
これまで、React Server Component 内でデータをクエリし、それをクライアントコンポーネントに渡して初期クエリ値として使用する方法を学びました。しかし、クエリのロジックが Home
React Server Component と Users
クライアントコンポーネントの二箇所に存在することにお気づきでしょう。これは、1箇所のクエリ変更を反映する際に更新漏れを起こしやすく、保守が難しくなります。
この重複を避けるために、Squid React SDK は withServerQuery
関数を公開しています。このフックは、サーバー上でデータをクエリし、その結果をクライアントコンポーネントに渡す処理を自動で行います。
新しい withServerQuery
関数を使用するように Home
ページを更新します。この関数は以下の 3 つの引数を取ります:
- クエリデータを受け取るクライアントコンポーネント。
- 実行するクエリ。
- クエリのアップデートに購読するかどうか。
この関数は、その後 Higher Order Component を生成し、Users
コンポーネントをレンダリングします。以下のように app/page.tsx
を更新してください:
import Users from '@/components/users';
import { Squid } from '@squidcloud/client';
import { getOptions } from '@/utils/squid';
import { withServerQuery } from '@squidcloud/react';
type User = {
id: string;
};
export default async function Home() {
const squid = Squid.getInstance(getOptions());
const UsersWithQuery = withServerQuery(
Users,
squid.collection<User>('users').query().dereference(),
true
);
return <UsersWithQuery />;
}
この新しい関数を使用するために、Users
コンポーネントにはいくつかの変更が必要です:
- コンポーネントから
useQuery
フックを削除します。データは、ラッピングしているwithServerQuery
関数によって供給されます。 users
プロップの名前をdata
に変更します。withServerQuery
は、ラップしたクライアントコンポーネントにdata
プロップを渡します。- プロップの型を
WithQueryProps<User>
に更新します。これは、基本的には{ data: Array<User> }
と同等です。 if (loading) {...}
とif (error) {...}
の条件分岐を削除します。これらは、コンポーネントがレンダリングされる前にデータがロードされるため、もはや必要ありません。
結果として、新しい components/users.tsx
コンポーネントは以下のようになります:
'use client';
import { useCollection, WithQueryProps } from '@squidcloud/react';
type User = {
id: string;
};
export default function Users({ data }: WithQueryProps<User>) {
const collection = useCollection<User>('users');
const insertUser = async () => {
await collection.doc().insert({
id: crypto.randomUUID(),
});
};
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<button onClick={insertUser}>Insert</button>
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
Next.js アプリをリロードすると、Loading… インジケーターもデータのちらつきもなく、すべてのユーザーが表示されます。また、Insert をクリックしてもユーザー一覧は動的に更新されます。
クエリの購読の動作を実験したい場合は、withServerQuery
関数内の true
を false
に変更してみてください。この場合、初期ロード時にはユーザーデータは表示されますが、ユーザー挿入時にリストがリアルタイムに更新されなくなります(ユーザーはページリロード後に表示されます)。これは、クライアントコンポーネントでの変更購読を行っていないためであり、実際には Squid クエリはクライアント側で実行されなくなるためです。
購読していない場合、実質的にサーバー側のみで Squid を使用していることになり、特にリアルタイムなデータ更新が不要な場合には、完全に有効なユースケースです。
サーバーでのデータ挿入
サーバーでデータをクエリするだけでなく、Squid を使用してルーターのハンドラーやサーバーアクション内でデータを挿入することもできます。ここでは、サーバーからユーザーを挿入してみましょう。
Route handlers
app/api/insert/route.ts
に新しいルーターハンドラーを作成し、以下のコードに置き換えてください:
import { getOptions } from '@/utils/squid';
import { Squid } from '@squidcloud/client';
import { NextResponse } from 'next/server';
type User = {
id: string;
};
export async function POST() {
const squid = Squid.getInstance(getOptions());
const user = {
id: crypto.randomUUID(),
};
await squid.collection<User>('users').doc().insert(user);
return NextResponse.json(user);
}
このコードは、Users
コンポーネント内の insertUser
関数と非常に似ています。基本的に、組み込みデータベース内にユーザーを作成するという同じ目的を果たしていますが、今回はクライアントではなくサーバー上で実行されています。
クライアント側からこの機能を呼び出すために、insertUser
関数を以下のように更新してください:
export default function Home(...) {
...
const insertUser = async () => {
await fetch("api/insert", { method: "POST" });
};
...
}
これで、Insert ボタンをクリックするとサーバーからユーザーが挿入されます!
楽観的アップデート
ボタンをクリックしてからユーザー一覧に新しく挿入されたユーザーが表示されるまで、若干の遅延があることにお気づきかもしれません。これは、Squid がクライアント側で自動的に楽観的アップデートを処理するためです。
クライアント側で直接 insertUser
を実装した場合、Squid は挿入処理を楽観的に実行し、リクエストが完了する前でも新しいユーザーを即座に表示します。もし何らかの理由で挿入が失敗した場合、Squid は楽観的な挿入をロールバックします。
サーバー側から挿入を行う場合、楽観的アップデートの恩恵を受けることができません。一般的には、Squid を API ルート内で使用して挿入することも可能ですが、クライアント側から直接挿入・更新を行う方がユーザーエクスペリエンスとして優れています。
Server Actions
ルーターのハンドラーに加え、App Router は実験的な Server Actions をサポートしています。Server Actions を使用すると、サーバー上で動的に実行される関数を記述することができます。
Server Actions をサポートするために、next.config.js
を以下のように更新します:
module.exports = {
experimental: {
serverActions: true,
},
};
actions
フォルダを新しく作成し、insert.tsx
というファイルを追加して、以下のコードを記述してください。use server
ディレクティブは、このファイルが Server Action を表していることを示します。
'use server';
import { Squid } from '@squidcloud/client';
import { getOptions } from '@/utils/squid';
type User = {
id: string;
};
export default async function insertUser() {
const squid = Squid.getInstance(getOptions());
await squid.collection<User>('users').doc().insert({
id: crypto.randomUUID(),
});
}
クライアント側では、Server Actions は form
を送信することでインポートされ、呼び出すことができます。先ほど作成した insertUser
アクション関数を呼び出すために、Users
コンポーネントを以下のように更新してください:
"use client";
import insertAction from '@/actions/insert';
...
export default function Users({ data }: WithQueryProps<User>) {
...
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<form action={insertAction}>
<button type="submit">Insert</button>
</form>
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
Server Actions はサーバー側で実行されるため、楽観的アップデートはサポートされません。
ページ
SquidをNext.jsと共に使用する場合、アプリケーションのサーバーサイドとクライアントサイドの両方でSquidクライアントにアクセスできます。サーバー側では、初期ペイロードの一部としてデータをクエリできます。クライアント側では、クエリやミューテーションを実行したり、リアルタイムのデータ更新をストリーミングしたりすることが可能です。
まずは、pages/_app.tsx
内でComponent
をSquidContextProvider
でラップします。プレースホルダーをSquidの設定オプションに置き換えてください。これらの値は、Squid Consoleまたは.env
ファイル内にあります。.env
ファイルはSquidバックエンドの作成中に自動生成され、バックエンドディレクトリ内に配置されています。
import '@/styles/globals.css';
import type { AppProps } from 'next/app';
import { SquidContextProvider } from '@squidcloud/react';
export default function App({ Component, pageProps }: AppProps) {
return (
<SquidContextProvider
options={{
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
}}
>
<Component {...pageProps} />
</SquidContextProvider>
);
}
ユーザーのクエリ
アプリにusers
コレクションへのクライアントサイドクエリを導入するために、pages/index.tsx
を以下のコードに置き換えてください:
import { useCollection, useQuery } from '@squidcloud/react';
type User = {
id: string;
};
export default function Home() {
const collection = useCollection<User>('users');
const { loading, data, error } = useQuery(collection.query().dereference());
if (loading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
Loading...
</div>
);
}
if (error) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
{error.message}
</div>
);
}
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
標準では、Squidは組み込みのデータベースを提供します。この例では、Users
クライアントコンポーネント内でuseQuery
フックを使用して、データベースのusers
コレクションに対してクエリを実行しています。フックはクエリとブール値を受け取り、この値はテーブルのライブアップデートを購読するかどうかを示します。query().dereference()
を使用することで、クエリの生のデータが返されます。
ウェブアプリでは、"Users"という見出しが表示されますが、ユーザーは表示されません!まだコレクションにユーザーを挿入する必要があります。
もし「Loading…」やエラーメッセージが表示された場合は、Squidバックエンドが起動しているか確認してください。tutorial-backend
に移動して、squid start
を実行してください。詳細はRunning the projectを参照してください。
ユーザーの挿入
データベースにユーザーを挿入する関数をトリガーするボタンコンポーネントを追加します。pages/index.tsx
に以下を追加してください:
import { useCollection, useQuery } from "@squidcloud/react";
...
export default function Home() {
...
const insertUser = async () => {
await collection.doc().insert({
id: crypto.randomUUID(),
});
}
...
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<button onClick={insertUser}>Insert</button>
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
ウェブアプリにはInsertボタンが表示されます。ボタンをクリックすると、ランダムなIDを持つユーザーが挿入されます。データベースクエリはライブアップデートに購読されるため、ボタンをクリックするとすぐにユーザーIDがユーザー一覧に現れます。collection.doc().insert(...)
の実行により、ユーザーデータがアプリケーションの組み込みデータベースに永続化されるので、ページをリフレッシュしてもユーザー一覧は保持されます。
サーバーでのクエリ実行
ページをリフレッシュすると、ユーザー一覧が表示される前に一瞬**Loading…**インジケーターが表示されます。これはUsers
コンポーネントがクライアントコンポーネントであり、クライアント側でユーザーのクエリを実行するのに短い時間がかかるためです。Next.jsのApp Routerを使用すると、このクエリをReact Server Component内で実行し、ページロード時にサーバーからクライアントへデータを渡すことができます。
pages/index.tsx
に新たにstatic getServerSideProps
関数を作成します。この関数は初期ユーザーデータのためにSquidにクエリを実行し、その後users
のリストをHome
コンポーネントの初期レンダリングに渡します:
import { useCollection, useQuery } from "@squidcloud/react";
import { Squid } from "@squidcloud/client";
...
export const getServerSideProps = (async () => {
const squid = Squid.getInstance({
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
});
const users = await squid
.collection<User>("users")
.query()
.dereference()
.snapshot();
return { props: { users } };
}) satisfies GetServerSideProps<{
users: Array<User>;
}>;
export default function Home({
users,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
...
}
最初にアプリをセットアップしたとき、SquidContextProvider
を使用してクライアント側でSquidを初期化しました。しかし、React Server ComponentsはReact Contextにアクセスできないため、このSquidのインスタンスはHome
ページ内からアクセスできません。そのため、@squidcloud/client
パッケージを使用して別のインスタンスを作成する必要があります。コードの重複を減らすため、Squidのオプションを取得するための共有ユーティリティの作成を推奨します。
utils
フォルダを作成し、squid.ts
というファイルを追加してください。新たなファイルに以下のコードを追加します:
import { SquidOptions } from '@squidcloud/client';
export function getOptions(): SquidOptions {
return {
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
};
}
その後、options={...}
の箇所をoptions={getOptions()}
に置き換えることができます。
pages/_app.tsx
で、getOptions
関数をインポートし、SquidContextProvider
のoptions
として渡します:
import { getOptions } from "@/utils/squid";
...
export default function App({ Component, pageProps }: AppProps) {
return (
<SquidContextProvider options={getOptions()}>
<Component {...pageProps} />
</SquidContextProvider>
);
}
サーバーでクエリ実行して取得したusers
にアクセスできるようになったので、useQuery
フックに初期値を渡すように更新できます:
...
export default function Home({
users,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
...
const { loading, data, error } = useQuery(
collection.query().dereference(),
{ initialData: users }
);
...
if (loading && !data.length) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
Loading...
</div>
);
}
...
}
このコードブロックでは2点の変更が行われています:
users
のリストをuseQuery
フックに初期データとして渡しています。これにより、フックから返される初期のdata
が空の配列ではなくユーザー一覧になります。- デフォルトでは、初期値を
useQuery
に渡しても、クライアント側でのクエリ実行が成功するまでloading
はtrue
となるため、データが存在するかどうかで**Loading…**の条件を更新しています。data
の存在を確認することで、クライアント側でクエリがロード中でもサーバー側から渡されたユーザー一覧を表示できます。
これらの変更により、ページをリフレッシュしても**Loading…**インジケーターは表示されず、ページが読み込まれるとすぐにユーザー一覧が表示されます。
サーバーでのデータ処理
squid
をgetServerSideProps
内で使用するだけでなく、APIルートでも使用できます!クライアント側ではなくAPIルートからユーザーを挿入してみましょう。
- デフォルトでNext.jsは
pages/api/hello.ts
ファイルを生成します。このファイルの名前をinsert.ts
に変更してください。 pages/api/insert.ts
を以下のコードに置き換えてください:
import type { NextApiRequest, NextApiResponse } from 'next';
import { getOptions } from '@/utils/squid';
import { Squid } from '@squidcloud/client';
type User = {
id: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<User>
) {
const squid = Squid.getInstance(getOptions());
const user = {
id: crypto.randomUUID(),
};
await squid.collection<User>('users').doc().insert(user);
res.status(200).json(user);
}
このコードは、Home
コンポーネント内のinsertUser
関数と非常に似ています。目的は同じで、組み込みデータベースにユーザーを作成することですが、実行されるのはクライアント側ではなくサーバー側です。
クライアントからこの機能を呼び出すために、insertUser
関数を以下のように更新します:
export default function Home(...) {
...
const insertUser = async () => {
await fetch("api/insert", { method: "POST" });
};
...
}
「Insert」ボタンをクリックしてユーザーを挿入してください。今回はサーバー側が挿入を処理します。
Optimistic updates
ボタンをクリックしてから、新しく挿入されたユーザーが一覧に表示されるまでに若干の遅延があることに注意してください。これは、Squidがクライアント側で自動的に楽観的アップデート (optimistic updates) を処理しているためです。
クライアント側でのinsertUser
の実装では、挿入はクライアント上で直接行われます。この場合、Squidは楽観的に挿入を実行し、挿入リクエストがまだ進行中であっても新しいユーザーを即座に表示します。もし何らかの理由で挿入に失敗した場合、Squidは楽観的挿入を取り消します。
サーバー側から挿入を行う場合、楽観的アップデートの利点を失います。一般的に、APIルート内でSquidを使用して挿入することは可能ですが、クライアント側から直接挿入および更新を行う方がユーザー体験としては優れていることが多いです。
And that's it!
Next.js と Squid の使い方を学んだこと、おめでとうございます!両者を組み合わせることで、シンプルで合理化されたウェブ開発アプローチが実現できます。さあ、どんどん開発を楽しんでください!