ページ
Next.js で Squid を使用する場合、アプリケーションのサーバー側とクライアント側の両方で Squid client にアクセスできます。サーバーでは、初期ペイロードの一部としてデータをクエリできます。クライアントでは、クエリやミューテーション(mutation)を実行したり、リアルタイムのデータ更新をストリーミングで受け取ったりできます。
まず pages/_app.tsx で、Component を SquidContextProvider でラップします。プレースホルダーを Squid の設定オプションに置き換えてください。これらの値は Squid Console または .env ファイルで確認できます。.env ファイルは Squid backend の作成 時に自動生成され、backend ディレクトリに配置されています。
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 のクエリ
アプリに 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 コレクションをクエリします。このフックはクエリと boolean を受け取り、テーブルのライブアップデートに subscribe(購読)するかどうかを示します。query().dereference() を使用すると、クエリの生データが返されます。
Web アプリでは “Users” という見出しは表示されますが、ユーザーがいません! まだコレクションにユーザーを挿入する必要があります。
“Loading…” やエラーメッセージが表示される場合は、Squid backend を起動したか確認してください。tutorial-backend に移動して squid start を実行します。Running the project を参照してください。
ユーザーの挿入
データベースにユーザーを挿入する関数をトリガーする button コンポーネントを追加します。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>
);
Web アプリに Insert ボタンが表示されます。ボタンをクリックすると、ランダムな ID を持つユーザーが挿入されます。データベースクエリはライブアップデートに subscribe されているため、ボタンをクリックするとすぐにユーザー 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 を初期化しました。この Squid のインスタンスは Home ページ内からはアクセスできません。React Server Components は React Context にアクセスできないためです。代わりに、@squidcloud/client パッケージを使って別のインスタンスを作成する必要があります(Squid React SDK のインストール時に自動的にインストールされます)。コードの重複を減らすため、Squid options を取得する共有ユーティリティを作成することをおすすめします。
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 関数を import し、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が空配列ではなくユーザー一覧になります。- Loading… の条件を、データが存在するかどうかを確認するように更新します。デフォルトでは、
useQueryに初期値を渡しても、クライアント側でのクエリが成功するまでloadingはtrueのままです。dataの存在をチェックすることで、クライアント側のクエリがまだ loading 中でも、サーバーから渡されたユーザー一覧をレンダリングできます。
これらの変更により、ページをリフレッシュしても Loading… インジケーターは表示されません。代わりに、ページロード直後からユーザー一覧が表示されます。
サーバーでデータを扱う
getServerSideProps 内で Squid を使うことに加えて、API routes でも Squid を使えます! クライアントからではなく、API route からユーザーを挿入してみましょう。
- デフォルトで 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” ボタンをクリックしてユーザーを挿入してください。今度はサーバーが insert を処理します。
Optimistic updates
ボタンをクリックしてから、新しく挿入されたユーザーがユーザー一覧に表示されるまでに小さな遅延があることに気づくはずです。これは、Squid がクライアントで optimistic updates を自動的に扱う仕組みによるものです。
クライアントサイド実装の insertUser では、insert はクライアントで直接実行されます。この場合、Squid は optimistic に insert を行うため、insert リクエストがまだ処理中であっても新しいユーザーが即座に表示されます。何らかの理由で insert が失敗した場合、Squid は optimistic insert をロールバックします。
サーバーから insert すると、optimistic updates の恩恵を失います。一般に、API routes 内で Squid を使って insert することも可能ですが、多くの場合、クライアントから直接 insert / update したほうがユーザー体験は良くなります。