【2026年最新版】Shopifyアプリ開発 公式チュートリアル(React Router版)徹底解説

【初心者向け】Shopifyアプリ開発入門|React RouterでQRコードアプリを作る手順

約11分で読めます

Shopifyアプリを作りたいけれど、何から始めるか分からない。そんな方に向けて、公式テンプレートを使う手順を整理します。

この記事では、React RouterとPrismaを使い、QRコードを作成します。商品ページへの遷移と、スキャン数の計測まで実装します。

この記事で完成するQRコードアプリ

結論から言うと、管理画面付きのQRコードアプリが完成します。作成したQRコードは、商品ページやチェックアウトへ利用者を誘導できます。

  • QRコードの作成、編集、削除

  • Shopify商品との紐付け

  • 管理画面でのQRコード一覧表示

  • 公開URLでのQRコード表示

  • スキャン数の加算

  • 設定先へのリダイレクト

対象読者と前提知識

Shopifyアプリ開発を始めたい初心者を対象にしています。JavaScript、React、Node.jsの基本操作があると進めやすくなります。

所要時間の目安

所要時間は、環境や経験によって変わります。本記事では、環境構築から動作確認までを順番に確認します。

先に完成イメージを確認してから、必要な章へ進んでください。

目次|環境構築からスキャン計測まで

  1. 公式テンプレートでアプリを作る

  2. PrismaにQRCodeテーブルを追加する

  3. QRコードのサーバー処理を作る

  4. 管理画面に一覧ページを作る

  5. 作成・編集ページを作る

  6. 公開ページでQRコードを表示する

  7. スキャン数を計測してリダイレクトする

  8. 動作確認とトラブル対処

公式ドキュメントも確認しながら進めます。Shopify公式のReact Routerガイド

1. Shopify公式テンプレートでアプリを作る

まずは公式テンプレートで、認証済みの開発基盤を作ります。認証や管理画面への埋め込みに必要な構成が、最初から含まれています。

アプリのひな形を作成する

npm init @shopify/app@latest

コマンドを実行すると、対話形式で設定を選びます。Frameworkでは、React Routerを選択します。

  • Framework:React Router

  • 生成されたアプリ名を確認

  • 表示される案内に沿って設定

この段階では、独自機能より開発基盤の生成を優先します。

開発用サーバーを起動する

cd your-app
npm install
npm run dev
  • cd your-app:アプリのフォルダへ移動

  • npm install:依存ライブラリを導入

  • npm run dev:開発用サーバーを起動

起動後、ターミナルにPreview URLが表示されます。そのURLを開くと、Shopify管理画面内でアプリを確認できます。

Preview URLが開ければ、環境構築は完了です。次はQRコード情報の保存先を用意します。

2. PrismaにQRCodeテーブルを追加する

QRコードの設定は、PrismaとSQLiteで保存します。商品、遷移先、タイトル、スキャン数をDBに持たせます。

Prismaは、schema.prismaでDB構造を定義します。その定義をもとに、Node.jsからDBを操作できます。

QRCodeモデルを定義する

prisma/schema.prismaを開き、QRCodeモデルを追加します。

model QRCode {
  id               Int      @id @default(autoincrement())
  shop             String
  title            String
  productId        String
  productHandle    String
  productVariantId String
  destination      String
  scans            Int      @default(0)
  createdAt        DateTime @default(now())
}

各カラムの役割

  • id:自動採番する主キー

  • shop:ストアを識別する値

  • title:管理用タイトル

  • productId:紐付ける商品のID

  • productVariantId:商品のバリアントID

  • destination:商品ページなどの遷移先

  • scans:スキャン回数

  • createdAt:作成日時

shopを持たせることで、ストア単位でデータを検索できます。

セッション情報に関する注意点

公式ドキュメントでは、要点のみのカラムが示されています。実際には、テンプレートの構成に応じてfirstNameやemailなどのカラムも必要です。

以前のRemixライブラリと、React Router v7フレームワークでは構成が異なります。利用中のテンプレートと公式情報を照合してください。

マイグレーションを実行する

schema.prismaを書くだけでは、DBにテーブルは作られません。次のコマンドでSQLiteへ反映します。

npx prisma migrate dev --name add_qrcode
  • migrate dev:開発用DBへ反映

  • --name add_qrcode:変更履歴の名前

Prisma Studioで保存内容を確認する

Prisma Studioを使うと、DBの内容をブラウザで確認できます。

npm run prisma studio
http://localhost:5555

QRCodeテーブルにデータが入っているかを、画面上で確認できます。

DBを目視できるため、保存処理の確認がしやすくなります。次は、DBとShopify Admin APIをつなぐ処理を作ります。

3. QRコードのサーバー処理を作る

QRCode.server.jsに、QRコード機能の中核を集約します。画面側からDB操作やAPI処理を直接呼ばずに済みます。

  • PrismaでQRコードを取得、保存する

  • Admin APIで商品情報を補完する

  • QRコード画像をData URLで生成する

  • 入力値をバリデーションする

  • 保存と削除を処理する

モデル層の実装

// app/models/QRCode.server.js
import qrcode from "qrcode";
import invariant from "tiny-invariant";
import db from "../db.server";

export async function getQRCode(id, graphql) {
  const qrCode = await db.qRCode.findFirst({ where: { id } });
  if (!qrCode) return null;
  return supplementQRCode(qrCode, graphql);
}

export async function getQRCodes(shop, graphql) {
  const qrCodes = await db.qRCode.findMany({
    where: { shop },
    orderBy: { id: "desc" },
  });
  if (qrCodes.length === 0) return [];
  return Promise.all(qrCodes.map((qrCode) => supplementQRCode(qrCode, graphql)));
}

export function getQRCodeImage(id) {
  const url = new URL(process.env.SHOPIFY_APP_URL);
  url.pathname = `/qrcodes/${id}`;
  return qrcode.toDataURL(url.href);
}

export function getDestinationUrl(qrCode) {
  switch (qrCode.destination) {
    case "product":
      return `https://${qrCode.shop}/products/${qrCode.productHandle}`;
    case "checkout":
      return `https://${qrCode.shop}/cart/${qrCode.productVariantId}:1`;
    default:
      return `https://${qrCode.shop}`;
  }
}

async function supplementQRCode(qrCode, graphql) {
  const query = `#graphql
    query supplementQRCode($id: ID!) {
      product(id: $id) {
        title
        handle
        images(first: 1) {
          edges {
            node { url altText }
          }
        }
      }
    }
  `;
  const response = await graphql(query, {
    variables: { id: qrCode.productId },
  });
  const { data: { product } } = await response.json();
  const productDeleted = !product?.title;
  const productHandle = product?.handle || qrCode.productHandle || "";
  return {
    ...qrCode,
    productDeleted,
    productTitle: product?.title || "",
    productHandle,
    productImage: product?.images?.edges?.[0]?.node?.url || null,
    productAlt: product?.images?.edges?.[0]?.node?.altText || "",
    destinationUrl: getDestinationUrl({ ...qrCode, productHandle }),
    image: await getQRCodeImage(qrCode.id),
  };
}

export function validateQRCode(data) {
  const errors = {};
  if (!data.title) errors.title = "Title is required";
  if (!data.productId) errors.productId = "Product is required";
  if (!data.destination) {
    errors.destination = "Destination is required";
  } else if (!["product", "checkout"].includes(data.destination)) {
    errors.destination = "Destination must be 'product' or 'checkout'";
  }
  return errors;
}

export async function upsertQRCode({ id, shop, data }) {
  invariant(shop, "shop is required");
  const payload = {
    shop,
    title: data.title,
    productId: data.productId,
    productHandle: data.productHandle || "",
    productVariantId: data.productVariantId,
    destination: data.destination,
  };
  if (id) {
    return db.qRCode.update({ where: { id }, data: payload });
  }
  return db.qRCode.create({ data: payload });
}

export async function deleteQRCode(id) {
  return db.qRCode.delete({ where: { id } });
}

このファイルで押さえるポイント

  • findMany:ストア別にQRコードを一覧取得

  • graphql:商品名や画像を取得

  • supplementQRCode:表示用データを統合

  • toDataURL:画像ファイルなしでQR画像を生成

  • validateQRCode:必須項目を確認

画面側はモデル層を呼ぶだけにすると、処理の責務が分かれます。

4. 管理画面にQRコード一覧を作る

/appでは、保存済みのQRコードを一覧表示します。データがない場合は、作成ページへ進める空状態を表示します。

  • loaderで管理画面の認証を確認する

  • 現在のストアを特定する

  • DBからQRコードを取得する

  • 商品情報を補完して画面へ渡す

loaderの実装

export async function loader({ request }) {
  const { admin, session } = await authenticate.admin(request);
  const qrCodes = await getQRCodes(session.shop, admin.graphql);

  return { qrCodes };
}

authenticate.admin(request)は、管理画面からのアクセスを確認します。認証後は、ストア情報とAdmin API用のGraphQLクライアントを取得できます。

Polaris Web Componentsで表示する

<s-page><s-table>などのs-*は、Shopify管理画面向けのUIです。

余白、見出し、空状態、テーブルを整えやすくなります。まず機能を作り、UIの細部を後から調整できます。

一覧ページの実装要点

export default function Index() {
  const { qrCodes } = useLoaderData();
  return (
    <s-page heading="QR codes">
      <s-link slot="secondary-actions" href="/app/qrcodes/new">
        Create QR code
      </s-link>
      {qrCodes.length === 0 ? (
        <EmptyQRCodeState />
      ) : (
        <QRTable qrCodes={qrCodes} />
      )}
    </s-page>
  );
}

実際の一覧表示では、タイトル、商品、作成日、スキャン数を表示します。商品が削除済みの場合は、その状態も表示できます。

空の状態

データがある状態

一覧ページが表示できたら、管理画面の基本導線は完成です。次に、QRコードの作成と編集を実装します。

5. QRコードの作成・編集ページを作る

作成と編集は、1つのルートでまとめて扱えます。/newなら新規作成、数字のIDなら編集として処理します。

  • loader:初期データや既存データを取得

  • action:保存、削除、リダイレクトを処理

  • React:入力状態と商品選択UIを管理

新規作成と編集の切り替え

React Routerのloaderとactionを使うと、表示と送信処理を同じファイルに整理できます。

export async function loader({ request, params }) {
  const { admin } = await authenticate.admin(request);
  if (params.id === "new") {
    return { destination: "product", title: "" };
  }
  return await getQRCode(Number(params.id), admin.graphql);
}

保存と削除の処理

export async function action({ request, params }) {
  const { session, redirect } = await authenticate.admin(request);
  const data = {
    ...Object.fromEntries(await request.formData()),
    shop: session.shop,
  };
  if (data.action === "delete") {
    await db.qRCode.delete({ where: { id: Number(params.id) } });
    return redirect("/app");
  }
  const errors = validateQRCode(data);
  if (errors) {
    return new Response(JSON.stringify({ errors }), {
      status: 422,
      headers: { "Content-Type": "application/json" },
    });
  }
  const qrCode = params.id === "new"
    ? await db.qRCode.create({ data })
    : await db.qRCode.update({
        where: { id: Number(params.id) },
        data,
      });
  return redirect(`/app/qrcodes/${qrCode.id}`);
}

商品選択と保存ボタンの注意点

商品選択には、ShopifyのResource Pickerを使います。選択後は商品ID、バリアントID、タイトル、ハンドル、画像をフォーム状態へ保存します。

公式ドキュメントの実装では、formの送信とuseSubmitの二重送信に注意が必要です。ここでは、e.preventDefault()で通常送信を止め、認証情報を含むuseSubmitで送信します。

function handleSave(e) {
  e.preventDefault();
  const data = {
    title: formState.title,
    productId: formState.productId || "",
    productVariantId: formState.productVariantId || "",
    productHandle: formState.productHandle || "",
    destination: formState.destination,
  };
  submit(data, { method: "post" });
}

保存処理で認証エラーが出る場合は、送信方法を確認してください。useSubmitやuseFetchを使う構成と、formの動作が重複していないかを見直します。

6. 公開ページでQRコード画像を表示する

公開ルートは、Shopify管理画面の外からアクセスできるようにします。QRコードを読む人は、管理画面へログインしているとは限りません。

そのため、/qrcodes/:idではAdmin認証を使わず、DBからQRコードを取得します。

公開ページの実装

import invariant from "tiny-invariant";
import { useLoaderData } from "react-router";
import db from "../db.server";
import { getQRCodeImage } from "../models/QRCode.server";

export const loader = async ({ params }) => {
  invariant(params.id, "Could not find QR code destination");
  const id = Number(params.id);
  const qrCode = await db.qRCode.findFirst({ where: { id } });
  invariant(qrCode, "Could not find QR code destination");
  return {
    title: qrCode.title,
    image: await getQRCodeImage(id),
  };
};

export default function QRCode() {
  const { image, title } = useLoaderData();
  return (
    <>
      <h1>{title}</h1>
      <img src={image} alt="QR Code for product" />
    </>
  );
}

公開ページで画像が表示できれば、QRコードを外部へ配置できます。

7. スキャン数を加算して目的URLへ転送する

スキャン用ルートでは、計測とリダイレクトを一度に行います。画面を表示するのではなく、処理後に目的URLへ移動させます。

  1. QRコードIDを確認する

  2. DBから対象データを取得する

  3. scansを1増やす

  4. 商品ページなどへリダイレクトする

スキャン用loaderの実装

import { redirect } from "react-router";
import invariant from "tiny-invariant";
import db from "../db.server";
import { getDestinationUrl } from "../models/QRCode.server";

export const loader = async ({ params }) => {
  invariant(params.id, "Could not find QR code destination");
  const id = Number(params.id);
  const qrCode = await db.qRCode.findFirst({ where: { id } });
  invariant(qrCode, "Could not find QR code destination");
  await db.qRCode.update({
    where: { id },
    data: { scans: { increment: 1 } },
  });
  return redirect(getDestinationUrl(qrCode));
};

loaderは、ページ表示前に実行されるサーバー処理です。フロント側のJavaScriptが不要なため、スマートフォンや外部ブラウザからも利用できます。

計測URLは、生成したQRコードが指すURLとして利用します。実装後は、アクセスごとにscansが増えるか確認します。

8. 動作確認とトラブル対処

最後は、管理画面、公開ページ、スキャン処理の順で確認します。機能ごとに切り分けると、エラーの場所を特定しやすくなります。

管理画面の確認

  • /appにQRコード一覧が表示される

  • 作成ページを開ける

  • 必須項目を入力して保存できる

  • 保存後に編集画面へ移動する

  • QRコード画像と公開URLを確認できる

公開ページとスキャンの確認

  • /qrcodes/:idへログインなしでアクセスできる

  • QRコード画像が表示される

  • /qrcodes/:id/scanでエラーが出ない

  • Prisma Studioでscansが1増える

  • 商品ページまたはチェックアウトへ移動する

よくある確認ポイント

  • QRコードのURLが{APP_URL}/qrcodes/:id/scanになっている

  • SHOPIFY_APP_URLが.envに設定されている

  • QRCodeテーブルにデータがある

  • productIdとproductVariantIdが保存されている

  • destinationの値が許可された値になっている

認証エラーは送信処理を、表示エラーはloaderを確認します。DBの値はPrisma Studioで確認すると、切り分けが進みます。

9. まとめ|次にコードを試す

本記事では、React Router版のShopify公式テンプレートを使いました。Prisma、Admin API、Polaris Web Componentsを組み合わせ、QRコードアプリを構築しています。

  • アプリの初期構築

  • QRCodeテーブルの作成

  • 商品情報の取得

  • 管理画面の一覧と編集

  • 公開ページの表示

  • スキャン数の計測

まずはテンプレートを起動し、QRCodeテーブルを追加してください。その後、一覧、編集、公開、計測の順に進めると確認しやすくなります。

さらに学びたい方へ

実務では、Resource Picker、Billing、Webhook、Flow、Functions連携も登場します。セキュリティや運用設計など、公式ドキュメントだけでは判断しにくい論点もあります。

Shopifyアプリ開発を体系的に学びたい方は、オンラインスクールテックギークも選択肢です。

実案件レベルの設計や、React Router、Web Componentsの構成を学びたい方は、公式サイトで内容を確認してください。

次の行動:テックギークの公式サイトで学習内容を確認する

コードを試す際は、この記事の手順と公式ドキュメントを照合してください。質問やエラーの内容は、実行したコマンドとともに整理すると確認しやすくなります。

コメントを投稿

コメントは承認後に公開されます。