📗

LIFFをNext.js App Routerで構築する手順

2024/06/30に公開

はじめに

LINEヤフー社が提供しているLINE Front-end Framework(以下LIFF)のアプリを開発するにあたって、
Create LIFF Appという、所謂対話式で環境構築が行えるコマンドを公式が提供しております。

Next.jsを始め、NuxtやSvelteなど多くのライブラリに対応されているのですが、
いざNext.jsで進めると、執筆時点(2024/06/30)ではPage Router向けのセットアップとなってしまいます。

App Routerで使用するには一部自前でセットアップする必要があり、その際に行った手順について記します。

https://developers.line.biz/ja/docs/liff/cli-tool-create-liff-app/

手順

1. 公式に従い、Create LIFF Appを実行する

まずは公式のDocsに従い、npx @line/create-liff-appを実行して環境構築を行います。

  • Which template do you want to use? -> Next.js
  • Would you like to use App Router? -> Yes
  • LIFF IDは事前に用意 & 環境変数に設定してください。
  • それ以外はお好みで問題ありません。

ちなみに、npx @line/create-liff-appの処理実体としては、
create next appの生成物にcreate liff appでの生成物を加えたのみとなっているようで、処理が交差していることはないように見受けられます。
(これは2で記す生成物からの仮説です。)

参考

  • 以降の実例では、以下の選択を前提として行います。

2. インストールされた内容を確認する

以下のような構成になっているかと思います。
この時点では

  • Next.jsのApp Router向けのファイル
    • From create next app
  • LIFFを扱うためのPage Router向けのファイル
    • From create liff app

が混ざってしまっているので、全てのファイルをApp Router向けに変更します。

tree -I node_modules
.
├── README.md
├── next-env.d.ts
├── next.config.mjs
├── package.json
├── pages
│   ├── _app.tsx
│   └── index.tsx
├── postcss.config.mjs
├── public
│   ├── favicon.ico
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── styles
│   ├── Home.module.css
│   └── globals.css
├── tailwind.config.ts
├── tsconfig.json
└── yarn.lock

5 directories, 19 files

3. App Routerで扱えるように改修する

まず、LIFFの実体を扱えるようにするReact Contextを定義します。

src/contexts/GlobalContext.ts
"use client";

import { Liff } from "@line/liff";
import { createContext } from "react";

type GlobalContextType = {
  liff: Liff | null;
  liffError: string | null;
};

export const GlobalContext = createContext<GlobalContextType | undefined>(undefined);

次に、こちらのContextを扱いやすくするためにCustom Hookを用意します。

src/hooks/useGlobalContext.ts
import { GlobalContext } from "@/contexts/GlobalContext";
import { useContext } from "react";

export const useGlobalContext = () => {
  const value = useContext(GlobalContext);

  if (!value) {
    throw new Error("useGlobalContext must be used within a GlobalContextProvider");
  }

  return value;
};

次に、src/app/template.tsxを定義し、pages/_app.tsxの内容を移行します。

ちなみに、公式にも記載のある移行手順となります。

https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#nesting-layouts

  • コアな処理は自動生成されたものをそのまま移植しています。
  • use client;を付与してClient Componentとして定義します。
  • ReactのContextを使用して、LIFFオブジェクトを渡すようにします。これはLIFFオブジェクトを各ページ要素に渡すために必要です。
src/app/template.tsx
"use client";

import { GlobalContext } from "@/contexts/GlobalContext";
import { Liff } from "@line/liff";
import { useCallback, useEffect, useState } from "react";

export default function Template({ children }: { children: React.ReactNode }) {
  const [liffObject, setLiffObject] = useState<Liff | null>(null);
  const [liffError, setLiffError] = useState<string | null>(null);

  // Execute liff.init() when the app is initialized
  useEffect(() => {
    // to avoid `window is not defined` error
    import("@line/liff")
      .then((liff) => liff.default)
      .then((liff) => {
        console.log("LIFF init...");
        liff
          .init({ liffId: process.env.NEXT_PUBLIC_LIFF_ID! })
          .then(() => {
            console.log("LIFF init succeeded.");
            setLiffObject(liff);
          })
          .catch((error: Error) => {
            console.log("LIFF init failed.");
            setLiffError(error.toString());
          });
      });
  }, []);

  return (
    <GlobalContext.Provider value={{ liff: liffObject, liffError: liffError }}>
      <div>{children}</div>
    </GlobalContext.Provider>
  );
}

最後に、pagesディレクトリを削除します。

pages/index.tsxの内容で特に引き継ぐものはありません。

4. 3で作成したLIFFのモジュールをPage Componentに組み込む

src/hooks/useGlobalContext.tsを組み込み、各ページで扱えるようにセットアップします。

注意点としては、Clientで動作させる前提となるため、Client Component内で呼び出す必要があります。

ここではサンプルとして、src/app/_components/Liff.tsxというClient Componentを作成し、そちら経由でContextを呼び出してみます。

src/app/_components/Liff.tsx
"use client";

import { useGlobalContext } from "@/hooks/useGlobalContext";
import { FC } from "react";

export const Liff:FC = () => {
  const { liff, liffError } = useGlobalContext();

  return (
    <div>
      <h2>create-liff-app</h2>
      {liff && <p>LIFF init succeeded.</p>}
      {liffError && (
        <>
          <p>LIFF init failed.</p>
          <p>
            <code>{liffError}</code>
          </p>
        </>
      )}
      <a
        href="https://developers.line.biz/ja/docs/liff/"
        target="_blank"
        rel="noreferrer"
      >
        LIFF Documentation
      </a>
    </div>
  );
}

次に、src/app/page.tsxを以下のように変更します。

src/app/page.tsx
import { Liff } from "@/app/_components/Liff";

export default function Home() {
  return (
    <main>
      <Liff />
    </main>
  );
}

5. 動作確認

Next.jsを起動し、/のパスを開きます。
以下のような表記になれば疎通成功です。
お持ちのLINEチャンネルに組み込んで、開発を進めましょう。

余談

  • この記事ではTailwind CSSを選択しておりますが、create liff appで生成される内容はCSS Modulesのファイルとなっております。CSSライブラリの移行に関しては特に特記事項もなく、ここでは割愛しております。
  • create liff appではパッケージ管理ツールとしてnpmyarnしか執筆時点では選択できませんが、pnpmなどへの移行も一般的な手順で可能です。(私は実際にpnpmを使用して開発を進めています)

後書き

App Routerは日々進化しているので、どんどん使っていきたいですね🙏
ご意見、ご提案などがございましたらお気軽にコメントいただけたら嬉しいです!

Discussion