Next.js と Squid
Squid は特定の意見に縛られないプラットフォームとして、お気に入りのフロントエンド/フルスタックフレームワークと一緒に利用できます。Next.js を使用している場合、Squid をプロジェクトに追加することで、データソースへの接続を効率化し、追加のセキュリティを提供し、リアルタイムのデータ更新をサポートするなど、さまざまなことが可能になります。さらに Next.js 開発者をより良くサポートするために、Squid と Next.js をシームレスに開発できるようにするいくつかの hooks を追加しました。
TL;DR
このチュートリアルでは、Squid を Next.js アプリに統合する方法を学びます。内容は以下を含みます。
- クライアントに対するクエリ実行とリアルタイムでの更新ストリーミング
- ページ読み込み時に Next.js サーバーからデータをクエリする
- クライアントとサーバーの両方からデータを mutate(更新)する
このドキュメントは、Pages Router と App Router のどちらを使用しているかによって少し異なるため、以下で正しいオプションを選択してください。
新しい Next.js アプリを作成する
next-tutorial-projectという名前のルートプロジェクトディレクトリを作成します。
mkdir next-tutorial-project
next-tutorial-project ディレクトリに移動し、次のいずれかのコマンドを実行して Next.js アプリを作成します。
App Router の場合:
cd next-tutorial-project
npx create-next-app@latest next-tutorial --ts --tailwind --eslint --app --no-src-dir
Pages Router の場合:
cd next-tutorial-project
npx create-next-app@latest next-tutorial --ts --tailwind --eslint --no-src-dir
プロジェクトについて注意点がいくつかあります。
- このプロジェクトは TypeScript を使用していますが、ご自身のプロジェクトでは JavaScript も使用できます。
- Pages Router と App Router の両方が Squid と統合できます。このチュートリアルでは好みの方法を選択してください。
- プロジェクトは Tailwind CSS でスタイリングされています。
プロジェクトが作成できたら、next-tutorial ディレクトリに移動して Squid React SDK をインストールします。Squid React SDK は、Squid を React および Next.js プロジェクトに統合するための hooks とユーティリティを提供します。
cd next-tutorial
npm install @squidcloud/react
Squid バックエンドを作成する
- Squid Console に移動し、
next-tutorialという名前で新しい application を作成します。
Squid は 2 つの異なる target environment(ターゲット環境)を提供します。開発用の dev と本番用の prod です。このチュートリアルでは開発向けに設計されている dev environment を使用します。アプリケーションが動作するために、プロジェクト全体を通して dev environment を使用していることを確認してください。詳細は、Squid の environments を参照してください。
-
Squid Console で application overview ページに移動し、Backend project セクションまでスクロールします。Initialize backend をクリックし、初期化コマンドをコピーします。
-
ルートプロジェクトディレクトリに移動します。
cd ..
- コンソールからコピーしたコマンドを使って backend を初期化します。コマンドの形式は次のとおりです。
squid init next-tutorial-backend --appId [YOUR_APP_ID] --apiKey [YOUR_API_KEY] --environmentId dev --squidDeveloperId [YOUR_SQUID_DEVELOPER_ID] --region [YOUR_REGION (likely us-east-1.aws)]
プロジェクトを実行する
Squid プロジェクトをローカルで実行するには、クライアントアプリと backend の Squid プロジェクトの両方をローカルで実行する必要があります。
next-tutorial-backendディレクトリに移動し、squid startを使って backend を起動します。
cd next-tutorial-backend
squid start
- 新しいターミナルウィンドウを開き、
next-tutorialディレクトリに移動します。その後、アプリを実行します。
cd next-tutorial
npm run dev
Next.js アプリプロジェクトは http://localhost:PORT で実行されています。ここで PORT はターミナルにログとして表示されます。まだページにレンダリングされる内容を編集していないため、表示されるアプリは Next.js の starter project です。
Router
ここから先は、Next.js で App Router を使用しているか Pages Router を使用しているかによってチュートリアルの内容が分岐します。Next.js プロジェクト作成時に選択したオプションを選んでください。
- App Router
- Pages Router
アプリ
Squid を Next.js と一緒に使うと、アプリケーションのサーバー側とクライアント側の両方で Squid クライアントにアクセスできます。サーバーでは、初期ペイロードの一部としてデータをクエリできます。クライアントでは、クエリ、ミューテーション(mutations)、およびリアルタイムのデータ更新をストリーミングで受け取ることができます。
App Router を使うと、use client と use server ディレクティブを使用して、クライアントでレンダリングされるコンポーネントとサーバーでレンダリングされるコンポーネントを区別できます。React Server Components(RSCs)は、レンダリング前に非同期処理(データのクエリなど)を実行できる点がユニークです。このチュートリアルでは、まずクライアントで Squid を使用し、その後 React Server Components の使用に移行します。
まず app/layout.tsx で、children を SquidContextProvider でラップします。プレースホルダーを Squid の設定オプションに置き換えてください。これらの値は Squid Console か .env ファイルで確認できます。.env ファイルは Squid backend の作成 中に自動生成され、backend ディレクトリ内にあります。
import './globals.css';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { SquidContextProvider } from '@squidcloud/react';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<SquidContextProvider
options={{
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION',
environmentId: 'dev',
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
}}
>
{children}
</SquidContextProvider>
</body>
</html>
);
}
ユーザーをクエリする
次に、ユーザーのリストを表示する新しいコンポーネントを作成します。app ディレクトリ配下に新しく components ディレクトリを作り、users.tsx という新しいファイルを作成してください。新しいファイルに次のコードを追加します。
'use client';
import { useCollection, useQuery } from '@squidcloud/react';
type User = {
id: string;
};
export default function Users() {
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>
);
}
Users コンポーネントをレンダリングするために、app/page.tsx のコードをすべて次の内容に置き換えてください。
import Users from '@/components/users';
export default function Home() {
return <Users />;
}
Squid は、標準で built-in database(組み込みデータベース)を提供します。この例では、Users の client component 内で useQuery フックを使い、データベースの users コレクションに対するクエリを実行します。このフックはクエリと boolean を受け取り、その boolean はテーブルへのライブ更新にサブスクライブしたいかどうかを示します。query().dereference() を使うと、クエリの生データ(raw data)を返します。
Web アプリでは “Users” の見出しが表示されますが、ユーザーがいません! まだコレクションにユーザーを挿入していないためです。
“Loading…” やエラーメッセージが表示される場合は、Squid backend を起動しているか確認してください。tutorial-backend に移動して squid start を実行します。Running the project を参照してください。
ユーザーを挿入する
データベースにユーザーを挿入する関数をトリガーする button component を追加します。components/users.tsx に次を追加してください。
import { useCollection, useQuery } from "@squidcloud/react";
...
export default function Users() {
...
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(...) を実行すると、ユーザーデータがアプリケーションの built-in database に永続化されるため、ページをリロードしてもユーザー一覧は保持されます。
サーバーでクエリを実行する
ページをリロードすると、ユーザー一覧が表示される前に Loading…* インジケーターが一瞬表示されます。これは Users コンポーネントが client component であり、クライアント側でユーザーをクエリするのに少し時間がかかるためです。Next.js App Router では、このクエリを React Server Component 内で実行し、ページロード時にサーバーからクライアントへデータを渡すことができます。
app/page.tsx で Squid クエリを直接実行して初期ユーザーデータを取得し、Users コンポーネントの初回レンダリングに users のリストを渡します。
import Users from '@/components/users';
import { Squid } from '@squidcloud/client';
type User = {
id: string;
};
export default async function Home() {
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 <Users users={users} />;
}
アプリを最初にセットアップしたとき、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()} に置き換えられます。
app/layout.tsx で getOptions 関数を import し、SquidContextProvider の options として渡します。
import { getOptions } from "@/utils/squid";
...
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<SquidContextProvider options={getOptions()}>
{children}
</SquidContextProvider>
</body>
</html>
);
}
サーバーでクエリした users にアクセスできるようになったので、Users コンポーネントの useQuery フックを更新して初期値を受け取るようにします。
...
export default function Users({ users }: { user: Array<User> }) {
...
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… インジケーターは表示されなくなります。代わりに、ページロードと同時にユーザー一覧が表示されます。
withServerQuery を使って重複を最小化する
ここまでで、React Server Component でデータをクエリし、それを client component に渡して initial query value として使う方法を学びました。しかし、クエリのロジックが 2 か所(Home React Server Component と Users client component)に存在していることに気づくでしょう。これはメンテナンスが難しく、片方だけクエリを変更してもう片方の更新を忘れるといった問題が起きやすくなります。
重複を避けるために、Squid React SDK は withServerQuery 関数を公開しています。この hook はサーバー側でのデータクエリと、それを client component に渡す処理を担います。
Home ページを更新して新しい withServerQuery 関数を使いましょう。この関数は 3 つの引数を取ります。
- クエリデータを受け取る client component
- 実行するクエリ
- クエリ更新にサブスクライブするかどうか
この関数は、その後 Users コンポーネントをレンダリングするために使える Higher Order Component を生成します。app/page.tsx を更新して新しい関数を含めてください。
import Users from '@/components/users';
import { Squid } from '@squidcloud/client';
import { getOptions } from '@/utils/squid';
import { withServerQuery } from '@squidcloud/react';
type User = {
id: string;
};
export default async function Home() {
const squid = Squid.getInstance(getOptions());
const UsersWithQuery = withServerQuery(
Users,
squid.collection<User>('users').query().dereference(),
true
);
return <UsersWithQuery />;
}
この新しい関数を使うために、Users コンポーネント側にもいくつか変更が必要です。
useQueryフックをコンポーネントから削除します。データはwithServerQueryによるラップから供給されるようになります。usersprop をdataにリネームします。withServerQueryはラップした client component にdataprop を渡します。- prop type を
WithQueryProps<User>に更新します。これは実質的に{ data: Array<User> }に相当するラッパーです。 if (loading) {...}とif (error) {...}の条件分岐を削除します。コンポーネントがレンダリングされる前にデータがロードされるはずなので不要です。
結果として、新しい components/users.tsx は次のようになります。
'use client';
import { useCollection, WithQueryProps } from '@squidcloud/react';
type User = {
id: string;
};
export default function Users({ data }: WithQueryProps<User>) {
const collection = useCollection<User>('users');
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>
);
}
Next.js アプリをリロードしてください。Loading… インジケーターもデータのチラつきもなく、すべてのユーザーが表示されるようになります。さらに Insert をクリックすると、ユーザー一覧は引き続き動的に更新されます。
クエリへのサブスクライブがどのように動作するか試したい場合は、withServerQuery 関数内の true を false に変えてみてください。この場合でもロード時にユーザーデータは表示されますが、ユーザーを挿入してもユーザー一覧がライブ更新 されません(ページをリロードするとユーザーが存在します)。これは、client component で変更にサブスクライブしていないためであり、実際 Squid クエリはクライアントではまったく実行されません!
変更にサブスクライブしない場合、Squid を実質的にサーバー側だけで使用していることになり、リアルタイムのデータ更新が不要であれば、これは完全に有効なユースケースです。
サーバーでデータを挿入する
サーバーでデータをクエリするだけでなく、Squid を router handlers や server actions の中でも使用できます。サーバーからユーザーを挿入してみましょう。
Route handlers
app/api/insert/route.ts 配下に新しい router handler を作成し、この route を次のコードに置き換えてください。
import { getOptions } from '@/utils/squid';
import { Squid } from '@squidcloud/client';
import { NextResponse } from 'next/server';
type User = {
id: string;
};
export async function POST() {
const squid = Squid.getInstance(getOptions());
const user = {
id: crypto.randomUUID(),
};
await squid.collection<User>('users').doc().insert(user);
return NextResponse.json(user);
}
このコードは Users コンポーネントの insertUser 関数と非常によく似ていることに気づくでしょう。どちらも built-in database にユーザーを作成するという目的は同じですが、今度はクライアントではなくサーバーで実行されます。
クライアントからこの関数を呼び出すには、insertUser 関数を次のように更新します。
export default function Home(...) {
...
const insertUser = async () => {
await fetch("api/insert", { method: "POST" });
};
...
}
これで “Insert” ボタンをクリックすると、サーバーからユーザーが挿入されます!
Optimistic updates
ボタンをクリックしてから、新しく挿入されたユーザーが一覧に表示されるまでに小さな遅延があることに気づくでしょう。これは、Squid がクライアントで optimistic updates(楽観的更新)を自動的に扱う仕組みによるものです。
クライアント側実装の insertUser では、insert はクライアント上で直接行われます。この場合 Squid は楽観的に insert を実行するため、insert リクエストがまだ処理中(in flight)であっても新しいユーザーが即座に表示されます。そして、何らかの理由で insert が失敗した場合、Squid は楽観的 insert をロールバックします。
サーバーから挿入する場合、optimistic updates の利点は失われます。一般に、API routes の中で Squid を使って挿入することもできますが、ユーザー体験としてはクライアントから直接 insert / update する方が良い場合が多いです。
Server actions
Route Handler に加えて、App Router は実験的機能である Server Actions をサポートします。Server Actions により、サーバー上で動的に実行できる関数をユーザーが書けるようになります。
Server Actions を有効にするために、next.config.js を更新してください。
module.exports = {
experimental: {
serverActions: true,
},
};
actions という新しいフォルダを作成し、insert.tsx というファイルを追加して次のコードを記述してください。use server ディレクティブは、このファイルが Server Action を表すことを示します。
'use server';
import { Squid } from '@squidcloud/client';
import { getOptions } from '@/utils/squid';
type User = {
id: string;
};
export default async function insertUser() {
const squid = Squid.getInstance(getOptions());
await squid.collection<User>('users').doc().insert({
id: crypto.randomUUID(),
});
}
クライアントでは、Server Actions は form の submit によって import して呼び出すことができます。作成した insertUser action を呼び出すために、Users コンポーネントを次のように更新してください。
"use client";
import insertAction from '@/actions/insert';
...
export default function Users({ data }: WithQueryProps<User>) {
...
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<form action={insertAction}>
<button type="submit">Insert</button>
</form>
<span>Users</span>
<ul>
{data.map((user) => (
<li key={user.id}>{user.id}</li>
))}
</ul>
</div>
);
}
Server Actions はサーバー側で実行されるため、optimistic updates をサポートしません。
ページ
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 コレクションへのクライアントサイドクエリを導入するには、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” の見出しが表示されますが、ユーザーがいません。まだコレクションにユーザーを挿入する必要があります。
“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 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 コンポーネントの初期レンダリングに渡します。
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 を取得する共有 utility を作成することを推奨します。
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 hook が初期値を受け取れるように更新します。
...
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のリストを初期データとしてuseQueryhook に渡しています。これにより、hook が返す初期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” button をクリックしてユーザーを挿入してください。今回はサーバーが挿入処理を担当します。
Optimistic updates
button をクリックしてから、新しく挿入されたユーザーがユーザー一覧に表示されるまでに小さな遅延があることに気づくでしょう。これは、Squid がクライアントで optimistic updates を自動的に扱う仕組みによるものです。
クライアントサイド実装の insertUser では、挿入がクライアント上で直接実行されます。この場合 Squid は optimistic に insert を行い、insert リクエストがまだ処理中でも新しいユーザーが即座に表示されます。何らかの理由で insert が失敗した場合、Squid は optimistic insert を rollback します。
サーバーから insert する場合、optimistic updates の利点が失われます。一般に、API routes 内で Squid を使って insert することもできますが、ユーザー体験としてはクライアントから直接 insert / update する方が良いことが多いです。
これで完了です!
Next.js で Squid を使う方法を学んだことをお祝いします!両者を組み合わせることで、シンプルで合理化された Web 開発のアプローチを提供します。ぜひ取り組んで、開発を楽しんでください!