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

ページ

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

まず pages/_app.tsx で、ComponentSquidContextProvider でラップします。プレースホルダーを Squid の設定オプションに置き換えてください。これらの値は Squid Console または .env ファイルで確認できます。.env ファイルは Squid backend の作成 時に自動生成され、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 のクエリ

アプリに 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 コレクションをクエリします。このフックはクエリと boolean を受け取り、テーブルのライブアップデートに subscribe(購読)するかどうかを示します。query().dereference() を使用すると、クエリの生データが返されます。

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

Note

“Loading…” やエラーメッセージが表示される場合は、Squid backend を起動したか確認してください。tutorial-backend に移動して squid start を実行します。Running the project を参照してください。

ユーザーの挿入

データベースにユーザーを挿入する関数をトリガーする button コンポーネントを追加します。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 を持つユーザーが挿入されます。データベースクエリはライブアップデートに subscribe されているため、ボタンをクリックするとすぐにユーザー ID がユーザー一覧に表示されます。collection.doc().insert(...) を実行すると、ユーザーデータがアプリケーションの組み込みデータベースに永続化されるので、ページをリフレッシュしてもユーザー一覧は保持されます。

サーバーでクエリを実行する

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

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

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 を初期化しました。この Squid のインスタンスは Home ページ内からはアクセスできません。React Server Components は React Context にアクセスできないためです。代わりに、@squidcloud/client パッケージを使って別のインスタンスを作成する必要があります(Squid React SDK のインストール時に自動的にインストールされます)。コードの重複を減らすため、Squid options を取得する共有ユーティリティを作成することをおすすめします。

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.tsxgetOptions 関数を import し、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 に初期値を渡しても、クライアント側でのクエリが成功するまで loadingtrue のままです。data の存在をチェックすることで、クライアント側のクエリがまだ loading 中でも、サーバーから渡されたユーザー一覧をレンダリングできます。

これらの変更により、ページをリフレッシュしても Loading… インジケーターは表示されません。代わりに、ページロード直後からユーザー一覧が表示されます。

サーバーでデータを扱う

getServerSideProps 内で Squid を使うことに加えて、API routes でも Squid を使えます! クライアントからではなく、API route からユーザーを挿入してみましょう。

  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” ボタンをクリックしてユーザーを挿入してください。今度はサーバーが insert を処理します。

Optimistic updates

ボタンをクリックしてから、新しく挿入されたユーザーがユーザー一覧に表示されるまでに小さな遅延があることに気づくはずです。これは、Squid がクライアントで optimistic updates を自動的に扱う仕組みによるものです。

クライアントサイド実装の insertUser では、insert はクライアントで直接実行されます。この場合、Squid は optimistic に insert を行うため、insert リクエストがまだ処理中であっても新しいユーザーが即座に表示されます。何らかの理由で insert が失敗した場合、Squid は optimistic insert をロールバックします。

Tip

サーバーから insert すると、optimistic updates の恩恵を失います。一般に、API routes 内で Squid を使って insert することも可能ですが、多くの場合、クライアントから直接 insert / update したほうがユーザー体験は良くなります。