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

ページ

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 コレクションへのクライアントサイドクエリを導入するには、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 hook を使い、データベースの users コレクションに対してクエリを実行します。この hook はクエリと 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 button が表示されます。button をクリックすると、ランダムな ID を持つユーザーが挿入されます。データベースクエリはライブ更新に subscribe しているため、button をクリックするとすぐにユーザー 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 を取得する共有 utility を作成することを推奨します。

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 関数を 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 hook が初期値を受け取れるように更新します。

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 hook に渡しています。これにより、hook が返す初期 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” button をクリックしてユーザーを挿入してください。今回はサーバーが挿入処理を担当します。

Optimistic updates

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

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

Tip

サーバーから insert する場合、optimistic updates の利点が失われます。一般に、API routes 内で Squid を使って insert することもできますが、ユーザー体験としてはクライアントから直接 insert / update する方が良いことが多いです。