1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【LIFF】LIFFアプリの導入手順まとめ

1
Posted at

はじめに

今回は、LINEを使ったアプリ開発をする際の、LIFFの導入手順をまとめてみました。
少しでも参考になれば幸いです。

前提

・すでにLIFFアプリは作成済みであることを前提とさせていただきます。
・開発環境はDockerを使用していることを前提とさせていただきます。

開発用LIFFアプリの作成手順は、こちらの記事がわかりやすく参考になるかと思います。

また、今回はフレームワークにNuxt4を使用しています。
Nuxtの導入手順に関してはこちらをご参照ください。

Dockerの導入手順はこちらの記事をご参照ください。

1. @line/liff SDKをインストール

まずは必要なパッケージをインストールします。

環境差異を防ぐためコンテナ内で行います。

以下のコマンドを順に実行します。

zsh
// 開発コンテナを起動します
docker compose up --build

// コンテナへ入ります
// (別のターミナルを開き、プロジェクトディレクトリへ移動)
// Alpineベースでbashが無いならshが一般的です
docker compose exec {対象のコンテナ(サービス)名} sh

// インストールします
pnpm add @line/liff

// コンテナから出ます
exit

2. .env.exampleの追加

プロジェクトルートに以下の.env.exampleを追加し、.envファイルのヒントを示します。

.env.example
# LIFF (LINE Front-end Framework) ID
# LINE Developers Console > LIFF タブで発行した値を入れる
NUXT_PUBLIC_LIFF_ID=YOUR_LIFF_ID_HERE

NUXT_PUBLIC_LIFF_IDは、LIFF SDKが「どの LIFF アプリを初期化するか」を識別するために必要になります。

3. nuxt.config.tsの更新

nuxt.config.tsに以下の記述を追加します。

nuxt.config.ts
runtimeConfig: {
  public: {
    liffId: process.env.NUXT_PUBLIC_LIFF_ID || '',
  },
},

runtimeConfig:には「アプリ実行時に利用する環境変数」を定義します。
public:はクライアント側(ブラウザ側)からも参照可能にする設定です。

4. liffプラグインの作成

以下のような、app/plugins/liff.client.tsを作成します。

app/plugins/liff.client.ts
// ============================================================
// LINE Front-end Framework (LIFF) SDK を読み込み
// ============================================================
// @line/liff は LINE公式のフロントエンドSDKです。
// LINEアプリ内ブラウザとの連携機能を提供します。
//
// 例:
// - ログイン状態確認
// - ユーザープロフィール取得
// - LINEログイン
// - ID Token取得
// - LINE内ブラウザ判定
//
// 公式:
// https://developers.line.biz/ja/docs/liff/overview/
// ============================================================
import liff from '@line/liff'

// ============================================================
// Nuxt Plugin 定義
// ============================================================
// defineNuxtPlugin() は Nuxt 起動時に実行される
// プラグインを定義する関数です。
//
// plugins/liff.client.ts
// のように .client.ts にすることで、
// クライアント側(ブラウザ側)のみで実行されます。
//
// LIFF SDK は window/document を使用するため、
// SSR(Node.js側)では実行できません。
// ============================================================
export default defineNuxtPlugin(async () => {
  // ==========================================================
  // Runtime Config 取得
  // ==========================================================
  // useRuntimeConfig() は nuxt.config.ts の
  // runtimeConfig を取得する Nuxt公式Composableです。
  //
  // nuxt.config.ts 例:
  //
  // runtimeConfig: {
  //   public: {
  //     liffId: process.env.NUXT_PUBLIC_LIFF_ID || '',
  //   },
  // }
  //
  // public 配下はクライアント側でも参照可能です。
  // ==========================================================
  const config = useRuntimeConfig()

  // ==========================================================
  // LIFF ID 取得
  // ==========================================================
  // 環境変数から LIFF ID を取得します。
  //
  // .env 例:
  // NUXT_PUBLIC_LIFF_ID=2001234567-abcdefgh
  //
  // LIFF ID は LINE Developers コンソールで発行されます。
  // ==========================================================
  const liffId = config.public.liffId

  // ==========================================================
  // LIFF ID 未設定チェック
  // ==========================================================
  // LIFF ID が未設定の場合は
  // liff.init() を実行できないため、
  // 初期化をスキップします。
  //
  // これにより:
  // - 開発環境
  // - CI
  // - 一部Preview環境
  //
  // などでもアプリがクラッシュしません。
  // ==========================================================
  if (!liffId) {
    // ========================================================
    // 開発者向け警告ログ
    // ========================================================
    console.warn('[LIFF] NUXT_PUBLIC_LIFF_ID is not set. Skipping liff.init().')

    // ========================================================
    // Nuxtへ liff を注入
    // ========================================================
    // provide すると:
    //
    // const { $liff } = useNuxtApp()
    //
    // のように全体で利用可能になります。
    //
    // init未実行でも $liff 自体は存在するため、
    // 型エラーや参照エラーを防げます。
    // ========================================================
    return {
      provide: { liff },
    }
  }

  // ==========================================================
  // LIFF 初期化
  // ==========================================================
  // liff.init() は LIFF SDK の初期化処理です。
  //
  // 内部的には:
  // - LINEアプリとの接続
  // - LIFFセッション開始
  // - ID Token管理
  // - ログイン状態確認
  //
  // などを行います。
  //
  // 公式:
  // https://developers.line.biz/ja/reference/liff/#initialize-liff-app
  // ==========================================================
  try {
    // ========================================================
    // LIFF SDK 初期化実行
    // ========================================================
    await liff.init({ liffId })
  } catch (error) {
    // ========================================================
    // 初期化失敗時
    // ========================================================
    // 例えば:
    // - LIFF ID誤り
    // - Endpoint URL不一致
    // - 通信エラー
    // - LINE外ブラウザ問題
    //
    // など。
    //
    // try-catch により
    // アプリ全体のクラッシュを防止します。
    // ========================================================
    console.error('[LIFF] liff.init() failed', error)
  }

  // ==========================================================
  // Nuxt全体へ liff を注入
  // ==========================================================
  // これにより:
  //
  // const { $liff } = useNuxtApp()
  //
  // でアクセス可能になります。
  //
  // Vue Component 例:
  //
  // const { $liff } = useNuxtApp()
  //
  // if ($liff.isLoggedIn()) {
  //   const profile = await $liff.getProfile()
  // }
  // ==========================================================
  return {
    provide: { liff },
  }
})

// ============================================================
// TypeScript 型拡張
// ============================================================
// NuxtApp に $liff を追加します。
//
// これを書かない場合:
//
// Property '$liff' does not exist on type 'NuxtApp'
//
// のような型エラーになります。
// ============================================================
declare module '#app' {
  interface NuxtApp {
    // ========================================================
    // typeof liff
    // ========================================================
    // importした liff オブジェクトそのものの型を利用。
    //
    // これにより:
    //
    // $liff.login()
    // $liff.logout()
    // $liff.getProfile()
    //
    // などが完全に型補完されます。
    // ========================================================
    $liff: typeof liff
  }
}

このプラグインはブラウザ起動時に(アプリケーションレベルで)1回だけ呼ばれます。
(ページ遷移時は呼ばれず、画面リロード時には呼ばれます)

ブラウザアクセス
  ↓
Nuxt起動
  ↓
Plugin実行
  ↓
await liff.init()
  ↓
provide
  ↓
Page Component表示

5. app.vue(トップページ)の実装

最後に、トップページをデフォルトのNuxt Welcome画面からカスタマイズしていきます。
app.vueを以下の内容で上書きします。

app/app.vue
<script setup lang="ts">
const { $liff } = useNuxtApp()
const config = useRuntimeConfig()

const isReady = ref(false)
const isInClient = ref(false)
const initError = ref<string | null>(null)

onMounted(() => {
  if (!config.public.liffId) {
    initError.value = 'LIFF ID が未設定です。.env の NUXT_PUBLIC_LIFF_ID を設定してください。'
    isReady.value = true
    return
  }

  isInClient.value = $liff.isInClient()
  isReady.value = true
})

const openInLine = () => {
  if (!config.public.liffId) return
  window.location.href = `https://liff.line.me/${config.public.liffId}`
}
</script>

<template>
  <ClientOnly>
    <div v-if="isReady">
      <div v-if="initError" class="liff-error">
        <p>{{ initError }}</p>
      </div>
      <div v-else-if="isInClient" class="liff-app">
        <p>Hello from my-app</p>
      </div>
      <div v-else class="liff-guide">
        <h1>LINEアプリで開いてください</h1>
        <p>
          このコンテンツは LINE 内の LIFF アプリでのみご利用いただけます。<br>
          下のボタンをタップして LINE で開いてください。
        </p>
        <button type="button" class="liff-guide__button" @click="openInLine">
          LINEで開く
        </button>
      </div>
    </div>
    <template #fallback>
      <p>読み込み中...</p>
    </template>
  </ClientOnly>
</template>

<style scoped>
.liff-guide {
  max-width: 480px;
  margin: 40px auto;
  padding: 24px;
  text-align: center;
}

.liff-guide__button {
  margin-top: 16px;
  padding: 12px 24px;
  font-size: 16px;
  background-color: #06c755;
  color: #fff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.liff-error {
  max-width: 480px;
  margin: 40px auto;
  padding: 16px;
  color: #c00;
  text-align: center;
}
</style>

<template>内の<ClientOnly>は、Nuxtで「この中のコンポーネントはクライアント(ブラウザ)側だけで描画する」という意味です。
NuxtはデフォルトでSSR(サーバーサイドレンダリング)に対応していますが、以下のようなブラウザ専用コード
window.~, localStorage.~
を書くとSSR時にエラーになってしまうため、使用します。

同様に、<script>内のonMounted(() => {もブラウザでしか実行されないため、
console.log(window.location.href)などはonMounted内で実行するようにします。

<ClientOnly>onMountedを上手に使うことが、NuxtのSSR対応には必要です。(設定でssrはオフにできます)

各行の詳しい内容は割愛しますが、気になった方はAIに質問してみてください。

6. 実際にLINEでアクセスしてみる

最後に実際にLINEでアクセスしてみます。

① .envの作成

プロジェクトルートに.envを作成します。

.env
NUXT_PUBLIC_LIFF_ID=YOUR_LIFF_ID_HERE

② LIFFアプリのエンドポイントURLを登録

長くなってしまうためこの記事では省略しますが、こちらの記事がわかりやすいのでご参照ください。

③ アプリを起動し、LINEでアクセスする。

以下のコマンドを実行し、アプリを起動します(Docker使用時の例)

zsh
docker compose up --build

LIFF URL(以下の例)

(例)https://liff.line.me/2001234567-asdfghjk

を以下のサイトでQRコードにして、LINEのカメラで読み込むと、無事に表示されているかと思います。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?