みなさん、こんにちは! TDX 2026 では新機能「Salesforce Multi-Framework」が発表され、Salesforce の UI を React を使って開発することができるようになりました。
- Agentforce Vibes Developer Guide: Build a React App with Salesforce Multi-Framework (Beta)
注意: 2026年4月時点ではまだ Beta 版です。
この記事では、Developer Edition で作成したスクラッチ組織を使い、内部ユーザーが利用する想定のシンプルなアプリを作成する手順を紹介します。スクラッチ組織の作成方法については、かこのブログ記事「Developer Edition + スクラッチ組織で、複数の開発・検証組織を手にいれる」をご参照ください。ログインして表示言語を日本語化した状態から進めていきます。
事前準備
まずは VS Code で新規に「Standard」で作成したプロジェクトを用意します。

React External App や React Internal App もあるのですが、結構作り込まれたテンプレートが展開されます。構造を理解することがやや大変なので、今回はシンプルなテンプレートからの作成を紹介します。
このあと、スクラッチ組織を作成、もしくは既存のスクラッチ組織に接続します。組織にログインして機能有効化を行ってください。
- 設定 > アプリケーション > Salesforce Multi-Framework を使用した React 開発 (ベータ)
- 「Enable Beta」ボタンを押す

この機能はオフにできませんので、必ず Developer Edition や Sandbox などで試すようにしてください。
このあとはローカルの VS Code での操作を進めます。Standard で作成したプロジェクトで、スクラッチ組織に接続した状態にしておきます。
ステップ1: テンプレートを展開
まずターミナルを開き、次のコマンドを実行します。
sf template generate ui-bundle -n myreactapp -d "./force-app/main/default/uiBundles" -t reactbasic
force-app/main/default/myreactapp ディレクトリに必要なファイルが展開されます。

このディレクトリに移動して、install と run dev を実行します。
# ディレクトリに移動 cd force-app/main/default/uiBundles/myreactapp # 必要なライブラリをインストール npm install # ローカルで実行 npm run dev

ブラウザを開いて、表示された URL にアクセスしてみます。次のように表示されたら成功です。

ターミナルで Ctrl + C でローカルのサーバーを止めておきます。
ステップ2: ビルドとデプロイ
2026年4月25日時点ですが、graphqlClient.ts でエラーを検出しています。当該ファイルの13行目を次のように修正します。
修正前: const response = await data.graphql?.<TData, TVariables>(query, variables);
修正後: const response = await data.graphql?.<TData, TVariables>({query, variables});

それではビルドを行います。同じフォルダで次のコマンドを実行します。
npm run build
エラーが出なければ成功です。

それでは組織にデプロイしてみましょう。次のコマンドを実行します。(スクラッチ組織の場合のコマンドです)
sf project deploy start
Status が Succeeded で完了すれば成功です。

ステップ3: 動作確認
それでは組織にデプロイされた React アプリを見てみましょう。
- アプリケーションランチャー > Myreactapp

先ほどのローカルテストと同じように表示されれば成功です。

ステップ4: 取引先のデータを表示できるように修正
pages ディレクトリの下に「Accounts.tsx」ファイルを作成します。(Agentforce Vibes に作ってもらいましたので、切り詰めればもっとシンプルにできるかもしれません。)
import { useEffect, useState } from 'react';
import { executeGraphQL } from '@/api/graphqlClient';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Skeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
interface AccountNode {
Id: string;
Name: { value: string };
Website: { value: string | null };
}
interface AccountsData {
uiapi: {
query: {
Account: {
edges: Array<{
node: AccountNode;
}>;
};
};
};
}
const ACCOUNTS_QUERY = `query GetAccounts {
uiapi {
query {
Account {
edges {
node {
Id
Name {
value
}
Website {
value
}
}
}
}
}
}
}`;
export default function Accounts() {
const [accounts, setAccounts] = useState<AccountNode[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchAccounts = async () => {
try {
setLoading(true);
setError(null);
const data = await executeGraphQL<AccountsData, undefined>(ACCOUNTS_QUERY);
const accountNodes = data.uiapi.query.Account.edges.map(edge => edge.node);
setAccounts(accountNodes);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch accounts');
console.error('Error fetching accounts:', err);
} finally {
setLoading(false);
}
};
fetchAccounts();
}, []);
if (loading) {
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 className="text-3xl font-bold text-gray-900 mb-6">取引先一覧</h1>
<div className="space-y-2">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
</div>
);
}
if (error) {
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 className="text-3xl font-bold text-gray-900 mb-6">取引先一覧</h1>
<Alert variant="destructive">
<AlertTitle>エラー</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
</div>
);
}
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900">取引先一覧</h1>
全{accounts.length}件の取引先が登録されています
</div>
{accounts.length === 0 ? (
<Alert>
<AlertTitle>取引先がありません</AlertTitle>
<AlertDescription>
現在、登録されている取引先はありません。
</AlertDescription>
</Alert>
) : (
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead className="font-semibold">ID</TableHead>
<TableHead className="font-semibold">取引先名</TableHead>
<TableHead className="font-semibold">ウェブサイト</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{accounts.map(account => (
<TableRow key={account.Id}>
<TableCell className="font-mono text-sm">
{account.Id}
</TableCell>
<TableCell className="font-medium">
{account.Name.value}
</TableCell>
<TableCell>
{account.Website.value ? (
<a
href={account.Website.value}
target="_blank" rel="noopener noreferrer"
className="text-blue-600 hover:underline">
{account.Website.value}
</a>
) : ('—')}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
);
}

routes.tsx ファイルを次の内容に置き換えます。
import type { RouteObject } from 'react-router';
import AppLayout from '@/appLayout';
import Home from './pages/Home';
import Accounts from './pages/Accounts';
import NotFound from './pages/NotFound';
export const routes: RouteObject[] = [
{
path: '/',
element: <AppLayout />,
children: [
{
index: true,
element: <Home />,
handle: { showInNavigation: true, label: 'Home' },
},
{
path: 'accounts',
element: <Accounts />,
handle: { showInNavigation: true, label: '取引先' },
},
{
path: '*',
element: <NotFound />,
},
],
},
];

ステップ5: 再ビルドとデプロイ
ファイルの修正が完了したら、再度ビルドを行い組織にデプロイします。
# ビルドの実行 npm run build # デプロイの実行 sf project deploy start # もしソースファイルエラーやコンフリクトが発生した場合(コマンドを実行するディレクトリによって --source-dir の引数は要調整) sf project deploy start --source-dir . --ignore-conflicts
Status が Succeeded で完了すれば成功です。

ステップ6: 動作確認
スクラッチ組織の場合、取引先にはデータが何もないのでいくつか作成しておきます。

再度 Myreactapp を表示します。画面右上のメニューアイコンを押すと「取引先」選択肢が出現し、選択すると取引先の一覧が表示されれば成功です。


おわりに
今までは Lightnign Experience & Lightning Web Components と Visualforce に頼っていた Web ブラウザへの UI 提供ですが、React にも対応したことで、そのデータの表現力が格段に向上しました。いわゆる社内向けの UI はもちろん、Experience Cloud での社外向けサイトでの利用も可能です。正式リリースまで今しばらくお待ちください。
今回はすべて手動での作業手順でご紹介しましたが、もちろんバイブコーディングで開発を進めていくこともできます。Agentforce Vibes はもちろん、その他のコーディング支援ツールでも利用できるようにスキルを公開していたりしますので、ぜひご活用ください。
- Github: Agentforce Vibes Library
SFDX プロジェクトの新規作成を行う際に、React Internal App を選ぶとより作り込まれたテンプレートが展開されます。Agentforce で作成したエージェントとのチャットも行える UI も組み込まれています。こちらもぜひ試してみてください。

参考資料
- Agentforce Vibes Developer Guide: Build a React App with Salesforce Multi-Framework (Beta)
- Github: Agentforce Vibes Library
- Agentforce Vibes React (Open Beta): Quick Look
The post Salesforce Mult-Framework を使用した React による UI 開発 appeared first on Salesforce Developers Blog.