ページ
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を使用して挿入することは可能ですが、クライアント側から直接挿入および更新を行う方がユーザー体験としては優れていることが多いです。