メインコンテンツまでスキップ

ページ

Next.js と一緒に Squid を使用する場合、アプリケーションのサーバーサイドとクライアントサイドの両方で Squid client にアクセスできます。サーバー側では、初期ペイロードの一部としてデータをクエリすることができます。クライアント側では、クエリやミューテーションの実行、そしてリアルタイムのデータ更新のストリーミングが可能です。

最初に、pages/_app.tsx 内で、ComponentSquidContextProvider でラップします。プレースホルダーを Squid の設定オプションに置き換えてください。これらの値は Squid Console または .env ファイル内にあります。.env ファイルは creating the Squid backend の際に自動生成され、バックエンドディレクトリに配置されています。

pages/_app.tsx
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 を以下のコードに置き換えます:

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() を使用することで、クエリの生データが返されます。

Web アプリでは、「Users」という見出しは表示されますが、ユーザーは表示されません!まだコレクションにユーザーを挿入する必要があります。

Note

もし「Loading…」やエラーメッセージが表示された場合は、Squid バックエンドが起動しているか確認してください。tutorial-backend に移動し、squid start を実行してください。詳細は Running the project をご覧ください。

ユーザーの挿入

データベースにユーザーを挿入する関数をトリガーするボタンコンポーネントを追加します。pages/index.tsx に以下を追加してください:

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 を持つユーザーが挿入されます。データベースのクエリはライブ更新を購読しているため、ボタンをクリックするとすぐにユーザーIDがリストに表示されます。collection.doc().insert(...) を実行することで、ユーザーデータはアプリケーションの内蔵データベースに保存され、ページをリロードしてもユーザーリストが保持されます。

サーバー上でのクエリの実行

ページをリフレッシュすると、ユーザーリストが表示される前に一瞬 Loading... インジケーターが現れます。これは Users コンポーネントがクライアントコンポーネントであり、クライアント側でユーザーのクエリに若干の時間がかかるためです。Next.js App Router を使用すれば、このクエリを React Server Component 内で実行し、ページロード時にサーバーからクライアントへデータを渡すことが可能です。

pages/index.tsx に新たに static getServerSideProps 関数を作成します。この関数は初期のユーザーデータを取得するために Squid にクエリを送り、そして Home コンポーネントの初期レンダリングに users のリストを渡します:

pages/index.tsx
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>) {
...
}
Note

当初、アプリのセットアップ時に SquidContextProvider を使用してクライアント側で Squid を初期化しました。しかし、React Server Components は React Context にアクセスできないため、この Squid のインスタンスは Home ページ内では利用できません。その代わり、@squidcloud/client パッケージを使用して別のインスタンスを作成する必要があります。コードの重複を減らすために、Squid オプションを取得するための共通のユーティリティを作成することをお勧めします。

utils フォルダを作成し、squid.ts というファイルを追加します。新規ファイルに以下のコードを追加してください:

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 関数をインポートし、SquidContextProvideroptions として渡してください:

pages/_app.tsx
import { getOptions } from "@/utils/squid";

...

export default function App({ Component, pageProps }: AppProps) {
return (
<SquidContextProvider options={getOptions()}>
<Component {...pageProps} />
</SquidContextProvider>
);
}

サーバー側でクエリした users にアクセスできるようになったので、次に useQuery フックに初期値を受け取らせるよう更新します:

pages/index.tsx
...

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点が変更されています:

  1. users のリストを初期データとして useQuery フックに渡すことにより、フックから返される初期 data が空の配列ではなくユーザーのリストになるようにしています。
  2. Loading… 状態の条件を、データが存在するかどうかで確認するように更新しています。初期値を useQuery に渡した場合でも、クライアント側でデータが正常にクエリされるまでは loading の値が true になるため、データの存在をチェックすることで、クライアント側でクエリが実行されている間もサーバー側から取得したユーザーリストを表示できます。

これらの変更により、ページをリフレッシュしても Loading… インジケーターが表示されることはなくなり、ページ読み込み時にユーザーリストがすぐに表示されます。

サーバー側でのデータ処理

Squid を getServerSideProps 内で使用する以外にも、API ルート内で Squid を利用することができます!次は、クライアントからではなく API ルートからユーザーを挿入してみましょう。

  1. Next.js はデフォルトで pages/api/hello.ts ファイルを生成するので、このファイル名を insert.ts に変更します。
  2. 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 関数を次のように更新してください:

pages/index.tsx
export default function Home(...) {
...

const insertUser = async () => {
await fetch("api/insert", { method: "POST" });
};
...
}

「Insert」ボタンをクリックしてユーザーを挿入してください。今回はサーバーが挿入を処理します。

Optimistic updates

ボタンをクリックしてから新しく挿入されたユーザーがリストに現れるまで、若干の遅延が発生することにお気づきかもしれません。これは Squid がクライアント側で楽観的更新 (optimistic updates) を自動的に処理するためです。

クライアント側で insertUser を実行すると、挿入はクライアント上で直接行われます。この場合、Squid は楽観的に挿入を実行するため、挿入リクエストが完了する前に新しいユーザーが即座に表示されます。もし挿入に失敗した場合、Squid は楽観的挿入をロールバックします。

Tip

サーバー側から挿入を行う場合、楽観的更新の利点が失われます。一般的に、API ルート内で Squid を使って挿入することも可能ですが、ユーザー体験を向上させるためには、クライアント側から直接挿入・更新を行う方が望ましいです。