ウェブ上のWordPressサイトの数は現在8億超と言われています。これを使いこなす術はごまんとありますが、今回はサイトを効率的に複製する方法に迫りましょう。

KinstaのWordPress専用マネージドホスティングでは、ユーザーフレンドリーなコントロールパネル「MyKinsta」で簡単に管理することができます。

さらに、好みのアプリケーション開発技術とKinsta APIを使用して、WordPressサイトを大規模に複製することができます。今回の記事では、Kinsta APIとReact(人気のJavaScriptライブラリの1つ)を使用して、その仕組みをご紹介します。

構築するもの

こんなケースを想定します。あなたはWordPressの開発会社を経営しています。スターターテンプレートとして使えるサイトを1つ以上持っています。以下のような、WordPressサイト複製用のReactアプリケーションを作ってみましょう。

Kinsta APIでサイトを複製する(Reactアプリケーション)
Kinsta APIでサイトを複製する(Reactアプリケーション)

前提条件

この説明を理解するには、HTMLCSSJavaScriptの基本的な理解と、Reactに関するある程度の知識が必要です。また、Node.jsとnpm(Node Package Manager)またはyarnがPCにインストールされている必要があります。このプロジェクトの焦点は、UIの作成やスタイリングの詳細よりも、ReactとKinsta APIを使用してWordPressサイト複製アプリケーションを構築することです。

開発環境のセットアップ

ゼロからReactアプリケーションを作成しインターフェースを開発することもできますし、以下の手順で前述のGitテンプレートを利用することもできます。

  1. プロジェクトのGitHubリポジトリにアクセス
  2. Use this template」>「Create a new repository」を選択して、スターターコードをGitHubアカウント内のリポジトリにコピー(「include all branches」にチェックを入れる)
  3. リポジトリをローカルコンピュータにプルし、次のコマンドを使ってstarter-filesブランチに切り替える:git checkout starter-files
  1. 次のコマンドを実行して、必要な依存関係をインストール:npm install

インストールが完了したら、npm run start でプロジェクトをローカルコンピュータで起動できます。これで、http://localhost:3000/でプロジェクトが開けます。

プロジェクトファイルを理解する

srcフォルダはReactアプリケーションの心臓部であり、webpackが必要とするJavaScriptが含まれています。このフォルダの中にはApp.jsがあり、プロジェクトの2つのルートが設定されています。

srcフォルダの中には、componentspagesというサブフォルダがあります。componentsフォルダには、Home.jsxOperations.jsxページで使用されるHeader.jsxFooter.jsxなどのコンポーネントが含まれています。

今回の説明では、Home.jsxOperations.jsxにロジックを実装することに集中しましょう。スタイリングとルーティングはGitHubのスターターファイルにあります。

Home.jsxには2つのフィールドがあるフォームがあります─作成するサイトの名前と、MyKinstaアカウントにあるWordPressサイトの一覧を表示する選択フィールドです(この一覧はKinsta API経由で取得されます)。

フォームの送信ボタン(サイトを複製)をクリックすると、operation_idプロパティを含むオブジェクトが返されます。このIDと表示名が、Operations.jsxへのルートパラメータとして渡され、複製操作のステータスが報告されます。インターフェースには、WordPressの管理者ログインとサイトのホームページにアクセスするリンクもあります。

WP管理画面とサイトへのリンクが表示されている(Operationsページ)
WP管理画面とサイトへのリンクが表示されている(Operationsページ)

Kinsta APIを使ってWordPressサイトを複製する

Home.jsx内で、3つのAPIリクエストがKinsta APIに送信されます。最初のリクエストは、Kinstaアカウントのサイトの一覧を取得することです。これはstateに保存され、selectフィールドに反復されます。このリクエストは、useEffectフックを使用してページがレンダリングされた直後に行われます。

2番目と3番目のリクエストは、「サイトを複製」ボタンがクリックされると行われます。2番目のリクエストは、複製したいサイトの環境IDを取得します。3番目のリクエストは、その環境IDとサイトの表示名を使用して、サイトの複製を開始します。

ReactでKinsta APIを使う

この記事では、Kinsta APIの2つのエンドポイントを利用します。

  • /sites:すべてのサイトの一覧を返し、サイト環境IDを要求し、最後に既存のサイトを複製することができます。
  • /operations:操作ステータスを取得するのに使用します。たとえば、サイトの複製操作が進行中の場合、このエンドポイントを使用して操作のステータスを追跡し、いつ操作が終了するかを判断できます。

Kinsta APIを利用するには、企業ID(MyKinstaの「企業」>「請求先情報」>「企業ID」で確認できます)とAPIキーが必要です。Kinsta APIキーの取得方法はこちらです。

これらの認証情報を取得したら、Reactアプリケーションの環境変数として安全に保存するのが最善です。環境変数を設定するには、プロジェクトのルートフォルダに.envファイルを作成します。このファイルの中に、以下の行を追加します。

REACT_APP_KINSTA_COMPANY_ID = 'YOUR_COMPANY_ID' 
REACT_APP_KINSTA_API_KEY = 'YOUR_API_KEY'

プロジェクト内でこの環境変数にアクセスするには、process.env.THE_VARIABLEを使用します。例えば、REACT_APP_KINSTA_COMPANY_IDにアクセスするには、process.env.REACT_APP_KINSTA_COMPANY_IDを使用します。

Kinsta APIで既存のサイトを複製する

まず、useEffect Hookを使用してHome.jsxがレンダリングするときにすべてのサイトの一覧を取得し、それをstateに保存することから始めましょう。これを実現するには、useEffectフックとuseStateフックをインポートし、フェッチされるサイトの配列を格納するstateを作成します。

import { useState, useEffect } from 'react';
const [sites, setSites] = useState([]);

次に、JavaScript Fetch APIを用い、Kinsta APIに問い合わせるためにuseEffect Hookを使用します。まず、ヘッダーとKinsta API URLを格納する定数変数を2つ作成します。このページでKinsta APIに複数のリクエストを送信するので、繰り返しを避けるためにこれを行います。

const KinstaAPIUrl = 'https://api.kinsta.com/v2';
const headers = useMemo(() => {
    return {
        Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
    };
}, []);

上のコードでは、useMemoフックがヘッダーオブジェクトをメモしており、その値は一定なので、レンダリングごとに再評価する必要はありません。これでAPIリクエストを作成できます。

useEffect(() => {
    const fetchAllSites = async () => {
        const query = new URLSearchParams({
            company: process.env.REACT_APP_KINSTA_COMPANY_ID,
        }).toString();
        const resp = await fetch(
            `${KinstaAPIUrl}/sites?${query}`,
            {
                method: 'GET',
                headers
            }
        );
        const data = await resp.json();
        setSites(data.company.sites);
    };
    fetchAllSites();
}, [headers]);

上のコードでは、非同期関数fetchAllSitesを作成しています。この関数の中で、まず.envファイルから取得したqueryパラメータ(あなたの企業ID)を定義します。次に、queryパラメータを使用して、Kinsta APIの/sites エンドポイントにGETリクエストを行います。そして、レスポンスが先に作成したsites stateに保存されます。最後に、fetchAllSitesを呼び出しフェッチプロセスを開始します。

それでは、sites stateに格納された値をループ処理して、selectフィールドに値を入力してみましょう。表示名はユーザーに表示され、サイトIDはオプション値として使用されます。こうすることで、フォームが送信されたときに、選択したサイトのIDを使って環境の詳細情報を取得できます。

<div className="input-div">
    <label>サイトを選択</label>
    <span>複製するサイトを選択してください</span>
    <select className="form-control">
        <option> value=""></option>
        {sites && (
            sites.map((site) => {
                return (
                    <option> key={site.id} value={site.id}>{site.display_name}</option>
                )
            })
        )}
    </select>
</div>

フォーム送信の処理とフォームからの値の取得に進みましょう。そのためには、入力フィールドごとにstate変数を作成する必要があります。

const [selectedSiteId, setSelectedSiteId] = useState('');
const [displayName, setDisplayName] = useState('');

次に、valueonChange属性を各入力要素に追加して、フォームフィールドをそれぞれのstate値に紐付けます。フォームは次のようになります。

<form>
    <div className="form-container">
        <div className="input-div">
            <label>表示名</label>
            <span>サイトの識別に便利です。MyKinstaと一時ドメインでのみ使用されます。</span>
            <input type="text" className="form-control" value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
        </div>
        <div className="input-div">
            <label>サイトを選択</label>
            <span>複製するサイトを選択してください</span>
            <select className="form-control" value={selectedSiteId} onChange={(e) => setSelectedSiteId(e.target.value)}>
                <option value=""></option>
                {sites && (
                    sites.map((site) => {
                        return (
                            <option key={site.id} value={site.id}>{site.display_name}</option>
                        )
                    })
                )}
            </select>
        </div>
        <button className='btn'>サイトを複製</button>
    </div>
</form>

上のコードでは、各入力要素でvalue属性が(state変数に対応するかたちで)設定されており、onChange 属性は、ユーザーが入力フィールドを操作したときにstate値を変更するのに使用されます。

フォームの送信を処理するには、onSubmitメソッドをform要素に紐付けます。例えば以下の通りです。

<form> onSubmit={handleSubmission}>
    {/* form details */}
</form>

Kinsta APIに2つのAPIリクエストを行うhandleSubmissionメソッドを定義します。最初のリクエストは複製されるサイトの環境IDを取得し、2番目のリクエストは複製操作を実行します。

まず、環境IDを取得することから始めましょう。handleSubmissionメソッド内で、このリクエストを処理する非同期関数を作成します。この関数が、/sitesエンドポイントにGETリクエストを送信し、選択したサイトのIDを付加します。そして、/environmentsエンドポイントが続きます。

const handleSubmission = async (e) => {
    e.preventDefault();
    const fetchEnvironmentId = async (siteId) => {
        const resp = await fetch(
            `${KinstaAPIUrl}/sites/${siteId}/environments`,
            {
                method: 'GET',
                headers
            }
        );
        const data = await resp.json();
        let envId = data.site.environments[0].id;
        return envId;
    }
    let environmentId = await fetchEnvironmentId(selectedSiteId);
}

fetchEnvironmentIdはKinsta APIにGETリクエストを送信する非同期関数です。選択したサイトの環境をフェッチし、レスポンスから環境IDを抽出します。環境IDはenvId 変数に格納され、返されます。関数を呼び出すとき、その戻り値をenvId変数に代入します。

これで、Kinsta APIを使用して既存のサイトを複製できるようになります。ソースサイトに関する重要な情報(企業ID、表示名、環境ID)が把握できる状態です。

handleSubmissionメソッド内で、このAPIリクエストを処理するcloneExistingSiteという関数を作成します。このリクエストは /sites/cloneエンドポイントに送られます。これまでのリクエストとは異なり、このリクエストのヘッダーはContent-Typeapplication/jsonとして指定する必要があります。さらに、これはPOSTリクエストなので、APIに送信したいペイロードを含むリクエストボディを含める必要があります。以下がリクエストの構造です。

const handleSubmission = async (e) => {
    e.preventDefault();

    // 環境IDの取得

    const cloneExistingSite = async (env_Id) => {
        const resp = await fetch(
            `${KinstaAPIUrl}/sites/clone`,
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
                },
                body: JSON.stringify({
                    company: `${process.env.REACT_APP_KINSTA_COMPANY_ID}`,
                    display_name: displayName,
                    source_env_id: env_Id,
                })
            }
        );
        const data = await resp.json();
        navigate(`/operations/${displayName}/${data.operation_id}`)
        console.log(data);
    }
    cloneExistingSite(environmentId);
}

このコードでは、ペイロードオブジェクトをJSON文字列に変換するために、JSON.stringify()を使用しbodyリクエストが作成されています。そしてレスポンスがdata変数に格納されます。react-router-domライブラリのuseNavigateメソッドを使用して、displayNameoperation_idがルートパラメータとして渡されます。useNaviagteメソッドをインポートし、インスタンス化することも忘れてはなりません。

// 必要なメソッドをインポート 
import { useNavigate } from 'react-router-dom'; 

// useNavigateメソッドをインスタンス化
const navigate = useNavigate();

これで、フォームに情報を入力し「サイトを複製」ボタンをクリックすると、サイト複製プロセスが始まり、その結果がMyKinstaで確認できます。とはいえ、カスタムUI内でサイトの複製操作を追跡したいので、ルート経由で送信されたデータを使用して、Operations.jsxでこれを処理してみます。

Kinsta APIで操作状況を確認する

Operations.jsxで、react-router-domuseParamsメソッドを使用してルートからオペレーションIDを取得します。 このIDは、「サイトステータスの確認」ボタンがクリックされるたびにAPIリクエストを行うのに使用されます。

まず、useParamsメソッドをインポートし、displayNameoperationId変数をインスタンス化します。

// useParamsライブラリのインポート
import { useParams } from 'react-router-dom';

// パラメータのインスタンス化
const { displayName, operationId } = useParams();

次に、要求が行われたときの操作ステータスを格納するstateを作成します。

const [operationData, setOperationData] = useState({ message: "Operation in progress." });

上記のコードでは、stateがデフォルトのメッセージで初期化され、「サイトステータスの確認」ボタンがクリックされるまで表示されます。「サイトステータスの確認」ボタンにonClickイベントを追加し、ボタンがクリックされたときにcheckOperationメソッドを呼び出します。

<button> className='sm-btn' onClick={() => checkOperation()}>サイトステータスの確認</button>

次に、checkOperation関数を作成し、Kinsta APIに操作リクエストを行います。headersKinstaAPIUrl定数を変数に格納し、APIリクエストで使用します。

const KinstaAPIUrl = 'https://api.kinsta.com/v2';
const headers = useMemo(() => {
    return {
        Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
    };
}, []);

const checkOperation = async () => {
    const resp = await fetch(
        `${KinstaAPIUrl}/operations/${operationId}`,
        {
            method: 'GET',
            headers
        }
    );
    const data = await resp.json();
    setOperationData(data);
};

上記のコードでは、/operationsエンドポイントに操作IDでGETリクエストが送信され、レスポンスがoperationData stateに格納されます。これで、マークアップ内でデータを使用できるようになります。

<div className="services">
    <div className="details">
        <p>{operationData.message}..</p>
        <button> className='sm-btn' onClick={() => checkOperation()}>サイトステータスの確認</button>
    </div>
</div>

最後に、ルート経由で渡されたdisplayNameデータを使用して、新しいサイトのURLとWordPress管理画面のURLを作成します。どちらのリンクも新しいタブで開きます。

<div className="details">
    <a href={`http://${displayName}.kinsta.cloud/wp-admin/`} target="_blank" rel="noreferrer" className='detail-link'>
        <p>WordPress管理画面を開く</p>
        <FiExternalLink />
    </a>
    <a href={`http://${displayName}.kinsta.cloud/`} target="_blank" rel="noreferrer" className='detail-link'>
        <p>URLを開く</p>
        <FiExternalLink />
    </a>
</div>

これらの変更により、Operations.jsxがルートから操作IDを取得し、ボタンがクリックされたときにAPIリクエストを行い、オペレーションステータスを表示し、displayNameデータに基づいてWordPress管理画面とサイトのリンクを提供します。

アプリケーションをKinstaにデプロイする

アプリケーションをKinstaのアプリケーションホスティングプラットフォームにデプロイするには、お好みのGitサービスにプロジェクトをプッシュする必要があります。プロジェクトをGitHubGitLab、またはBitbucketのいずれかでホストし、Kinstaへのデプロイに進むことができます。

リポジトリをKinstaにデプロイするには、以下の手順に従ってください。

  1. MyKinstaでKinstaアカウントにログインするか、アカウントを作成
  2. 左側のサイドバーで、「アプリケーション」をクリックし「サービスを追加」をクリック
  3. ドロップダウンメニューから「アプリケーション」を選択して、ReactアプリケーションをKinstaにデプロイ
  4. 表示されたポップアップで、デプロイするリポジトリを選択(複数のブランチがある場合は、希望のブランチを選択 ※アプリケーションに名前を付けることも可)
  5. 25のデータセンターから選択
  6. Kinstaのシステムが自動でアプリケーションのstartコマンドを検出

最後に、注意点として、APIキーをGitサービスのようなパブリックホストにプッシュするのは安全ではありません。ホスティングする際には、.envファイルで指定された同じ変数名と値を使用して環境変数として追加することができます。

デプロイ時にMyKinstaで環境変数を設定する
デプロイ時にMyKinstaで環境変数を設定する

アプリケーションのデプロイを開始すると、プロセスが開始され、通常は数分以内に完了します。デプロイが成功すると、https://clone-wp-site-12teh.kinsta.app/ のようなアプリケーションへのリンクが生成されます。

まとめ

Kinsta APIを使って、既存のサイトを複製したり、WordPress環境の様々な側面を管理したり、WordPressサイトの管理に便利なユーザーインターフェースを作成したりすることができます。

この記事では、MyKinsta外でサイトを複製するアプリケーションの開発方法をご紹介しました。

あなたはKinsta APIをどのように使用していますか?どのような機能やエンドポイントをAPIに追加してほしいですか?コメント欄でお聞かせください。

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.