Help us understand the problem. What is going on with this article?

LINE の LIFF 開発超入門 : LIFF SDK を使った Web アプリの開発

More than 1 year has passed since last update.

本記事の概要

この記事では LINE で提供されている Line Front-end Framework (LIFF) を使ったページの開発方法をチュートリアル形式で記載しています。カバーする内容は LIFF の基本機能をはじめ、Bot との連携例や CLI ツールを使った登録から展開など、LIFF を使うにあたって必要な知識となります。

尚、本記事で利用するコードは line-liff-starter より一部拝借してします。
また最終系は こちらのレポジトリ にあります。

LIFF アプリとは

LIFF SDK を利用して開発した Web ページの事であり、登録時に取得できる専用のアドレスに LINE からアクセスすることで動作するアプリケーションとなります。つまり、LIFF アプリの開発は LIFF SDK を埋め込んだ Web ページを開発することです。

必要なスキルと環境

※ この記事では Microsoft Azure を利用しますが、LIFF を利用するサイトのホストは任意です。

事前準備

チャネルの作成

こちらのページ を参考にチャネルを作成します。チャネル作成後、チャネルアクセストークンを発行してコピーしておきます。

また「Your user ID」も後ほど利用するためコピーしておきます。

Azure 環境の準備

Azure ポータルを使い慣れている場合はポータルからの操作でもいいですが、今回は Azure CLI を使います。また Heroku など他のサービスを使っている場合はそちらでも結構です。

1. まだインストールしていない場合は、Azure CLI をインストール。もしくは Azure ポータル ログイン後、画面右上の Cloud Shell よりコンソールを起動。

2. Cloud Shall の場合は、手順 4 に進む。コンソールの場合は、まず以下のコマンドでログインを実行。

az login

3. 画面の指示に順ってログインを完了。

4. ログイン完了後、複数の Azure サブスクリプションがある場合は、以下のコマンドで使うサブスクリプションをセット。

az account list
az account set -s 'subscription id'
az account show

5. まず以下のコマンドでリソースグループを lineliffsample として、地域を東日本で作成。

az group create -n lineliffsample -l japaneast

6. 次に LIFF アプリケーションのデプロイ先となる WebApps を作成。作成時には、まずサービスプランを作成し、WebApp を作成します。名前はグローバルで一意になるものを指定。

az appservice plan create -n lineliffappplan -g lineliffsample --sku F1
az webapp create -n <ユニークな WebApp の名前> -g lineliffsample -p lineliffappplan

7. git でデプロイできるように指定。返り値が git のリモートレポジトリとなるため保存しておく。

az webapp deployment user set --user-name <ユーザー名> --password <パスワード>
az webapp deployment source config-local-git -n <ユニークな WebApp の名前> -g lineliffsample

LIFF CLI と既存のサイトを使った LIFF の登録およびテスト

LIFF は LIFF SDK を使った Web ページで最大効果を発揮しますが、既存のページを指定してすぐに使うことも可能です。ここではまず LIFF 登録の概念を把握するため、既存のページ (https://linecorp.com/ja/) を登録してみます。

1. Liff npm パッケージをまだ入れていない場合、npm でインストール。

npm install -g liff

2. liff init で初期化。上記で取得したチャネルのアクセストークンを渡す。

liff init <チャネルアクセストークン>

3. LIFF アプリケーションの登録。ここではサイズとして tall を指定。サイズは full, tall, compact がある。

liff add https://linecorp.com/ja/ tall

4. 戻り値から Liff ID および Accessible URL を確認。

5. 以下 liff send コマンドでリンクを送付するか、LIFF アドレスを手動で LINE へ送付。LINE アプリよりリンクをクリックしてテスト。

liff send <LIFF ID> <User ID>

LIFF アプリの開発

では LIFF SDK を使ったアプリを開発していきます。

プロジェクトの作成と LIFF SDK の読み込み

1. 任意のディレクトに MyFirstLiff フォルダを作成し、Visual Studio Code で開く。

mkdir MyFirstLiff
cd MyFirstLiff
code .

2. Visual Studio Code で新しく index.html ファイルを追加。

image.png

3. index.html の中身に以下のコードをペースト

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My First LIFF</title>
</head>
<body>
    <div id="liffdata">
        <h2>LIFF Data</h2>
        <table border="1">
            <tr>
                <th>language</th>
                <td id="languagefield"></td>
            </tr>
            <tr>
                <th>context.viewType</th>
                <td id="viewtypefield"></td>
            </tr>
            <tr>
                <th>context.userId</th>
                <td id="useridfield"></td>
            </tr>
            <tr>
                <th>context.utouId</th>
                <td id="utouidfield"></td>
            </tr>
            <tr>
                <th>context.roomId</th>
                <td id="roomidfield"></td>
            </tr>
            <tr>
                <th>context.groupId</th>
                <td id="groupidfield"></td>
            </tr>
        </table>
    </div>

    <!-- LIFF SDK の読み込み -->
    <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
    <script src="liff.js"></script>
</body>
</html>

4. 同じディレクトリに liff.js を追加し、以下コードをペースト。

window.onload = function (e) {
    // init で初期化。基本情報を取得。
    // https://developers.line.me/ja/reference/liff/#initialize-liff-app
    liff.init(function (data) {
        initializeApp(data);
    });
};

function initializeApp(data) {
    document.getElementById('languagefield').textContent = data.language;
    document.getElementById('viewtypefield').textContent = data.context.viewType;
    document.getElementById('useridfield').textContent = data.context.userId;
    document.getElementById('utouidfield').textContent = data.context.utouId;
    document.getElementById('roomidfield').textContent = data.context.roomId;
    document.getElementById('groupidfield').textContent = data.context.groupId;
}

5. 全ての保存。

LIFF アプリケーションの展開

この段階で一旦 LIFF アプリの公開と登録を行います。

1. まずレポジトリの初期化。Visual Studio Code より統合コンソールを開き、以下コマンドを実行。

git init

2. 次に発行先を設定。アドレスは WebApp 作成時に取得したものを使用。

git remote add origin <上記手順で取得したリモート git アドレス> 

3. ファイルの追加とコミット。

git add .
git commit -m "初回コミット"

4. Push でデプロイの実行。

git push -u origin master

5. LIFF CLI でアプリの登録。

liff add <Web App のアドレス> tall

6. 戻り値を liff send か手動で LINE に送付して SDK の動作を確認。ログインしているユーザーの ID や言語が取得できる。

SDK を使ったユーザープロファイルの取得

1. index.html のコードを以下に変更。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My First LIFF</title>
</head>
<body>   
    <div id="profileinfo">
        <h2>Profile</h2>
        <div id="profilepicturediv">
        </div>
        <table border="1">
            <tr>
                <th>userId</th>
                <td id="useridprofilefield"></td>
            </tr>
            <tr>
                <th>displayName</th>
                <td id="displaynamefield"></td>
            </tr>
            <tr>
                <th>statusMessage</th>
                <td id="statusmessagefield"></td>
            </tr>
        </table>
    </div>

    <div id="liffdata">
        <h2>LIFF Data</h2>
        <table border="1">
            <tr>
                <th>language</th>
                <td id="languagefield"></td>
            </tr>
            <tr>
                <th>context.viewType</th>
                <td id="viewtypefield"></td>
            </tr>
            <tr>
                <th>context.userId</th>
                <td id="useridfield"></td>
            </tr>
            <tr>
                <th>context.utouId</th>
                <td id="utouidfield"></td>
            </tr>
            <tr>
                <th>context.roomId</th>
                <td id="roomidfield"></td>
            </tr>
            <tr>
                <th>context.groupId</th>
                <td id="groupidfield"></td>
            </tr>
        </table>
    </div>

    <!-- LIFF SDK の読み込み -->
    <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
    <script src="liff.js"></script>
</body>
</html>

2. liff.js を以下のコードと差し替え。

window.onload = function (e) {
    // init で初期化。基本情報を取得。
    // https://developers.line.me/ja/reference/liff/#initialize-liff-app
    liff.init(function (data) {
        getProfile();
        initializeApp(data);
    });
};

// プロファイルの取得と表示
function getProfile(){
    // https://developers.line.me/ja/reference/liff/#liffgetprofile()
    liff.getProfile().then(function (profile) {
        document.getElementById('useridprofilefield').textContent = profile.userId;
        document.getElementById('displaynamefield').textContent = profile.displayName;

        var profilePictureDiv = document.getElementById('profilepicturediv');
        if (profilePictureDiv.firstElementChild) {
            profilePictureDiv.removeChild(profilePictureDiv.firstElementChild);
        }
        var img = document.createElement('img');
        img.src = profile.pictureUrl;
        img.alt = "Profile Picture";
        img.width = 200;
        profilePictureDiv.appendChild(img);

        document.getElementById('statusmessagefield').textContent = profile.statusMessage;
    }).catch(function (error) {
        window.alert("Error getting profile: " + error);
    });
}

function initializeApp(data) {
    document.getElementById('languagefield').textContent = data.language;
    document.getElementById('viewtypefield').textContent = data.context.viewType;
    document.getElementById('useridfield').textContent = data.context.userId;
    document.getElementById('utouidfield').textContent = data.context.utouId;
    document.getElementById('roomidfield').textContent = data.context.roomId;
    document.getElementById('groupidfield').textContent = data.context.groupId;
}

3. git で変更のコミットしてプッシュ。

git commit -am "add profile"
git push origin master

4. LIFF アプリケーションのアドレスは変わっていないため、先ほどテストで送ったアドレスをクリックして動作を確認。

LIFF よりメッセージの送信

LIFF 上のボタンクリックイベントなどから、チャットウィンドウにメッセージを送ることも出来ます。これで Bot と LIFF のシームレスな連携が可能となります。ここではテキストとスタンプを例として実装しますが、他のタイプもサポートされます。

詳細は liff.sendMessages() 参照。
1. index.html を以下のコードに差し替え。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My First LIFF</title>
</head>
<body>   
    <button id="sendmessagebutton">メッセージ送信</button>
    <div id="profileinfo">
        <h2>Profile</h2>
        <div id="profilepicturediv">
        </div>
        <table border="1">
            <tr>
                <th>userId</th>
                <td id="useridprofilefield"></td>
            </tr>
            <tr>
                <th>displayName</th>
                <td id="displaynamefield"></td>
            </tr>
            <tr>
                <th>statusMessage</th>
                <td id="statusmessagefield"></td>
            </tr>
        </table>
    </div>

    <div id="liffdata">
        <h2>LIFF Data</h2>
        <table border="1">
            <tr>
                <th>language</th>
                <td id="languagefield"></td>
            </tr>
            <tr>
                <th>context.viewType</th>
                <td id="viewtypefield"></td>
            </tr>
            <tr>
                <th>context.userId</th>
                <td id="useridfield"></td>
            </tr>
            <tr>
                <th>context.utouId</th>
                <td id="utouidfield"></td>
            </tr>
            <tr>
                <th>context.roomId</th>
                <td id="roomidfield"></td>
            </tr>
            <tr>
                <th>context.groupId</th>
                <td id="groupidfield"></td>
            </tr>
        </table>
    </div>

    <!-- LIFF SDK の読み込み -->
    <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
    <script src="liff.js"></script>
</body>
</html>

2. liff.js を以下のコードに差し替え。

window.onload = function (e) {
    // init で初期化。基本情報を取得。
    // https://developers.line.me/ja/reference/liff/#initialize-liff-app
    liff.init(function (data) {
        getProfile();
        initializeApp(data);
    });

    // メッセージの送信
    document.getElementById('sendmessagebutton').addEventListener('click', function () {
        // https://developers.line.me/ja/reference/liff/#liffsendmessages()
        liff.sendMessages([{
            type: 'text',
            text: "テキストメッセージの送信"
        }, {
            type: 'sticker',
            packageId: '2',
            stickerId: '144'
        }]).then(function () {
            window.alert("送信完了");
        }).catch(function (error) {
            window.alert("Error sending message: " + error);
        });
    });
};

// プロファイルの取得と表示
function getProfile(){
    // https://developers.line.me/ja/reference/liff/#liffgetprofile()
    liff.getProfile().then(function (profile) {
        document.getElementById('useridprofilefield').textContent = profile.userId;
        document.getElementById('displaynamefield').textContent = profile.displayName;

        var profilePictureDiv = document.getElementById('profilepicturediv');
        if (profilePictureDiv.firstElementChild) {
            profilePictureDiv.removeChild(profilePictureDiv.firstElementChild);
        }
        var img = document.createElement('img');
        img.src = profile.pictureUrl;
        img.alt = "Profile Picture";
        img.width = 200;
        profilePictureDiv.appendChild(img);

        document.getElementById('statusmessagefield').textContent = profile.statusMessage;
    }).catch(function (error) {
        window.alert("Error getting profile: " + error);
    });
}

function initializeApp(data) {
    document.getElementById('languagefield').textContent = data.language;
    document.getElementById('viewtypefield').textContent = data.context.viewType;
    document.getElementById('useridfield').textContent = data.context.userId;
    document.getElementById('utouidfield').textContent = data.context.utouId;
    document.getElementById('roomidfield').textContent = data.context.roomId;
    document.getElementById('groupidfield').textContent = data.context.groupId;
}

3. git で変更のコミットしてプッシュ。

git commit -am "add send message"
git push origin master

4. LIFF アプリケーションのアドレスは変わっていないため、先ほどテストで送ったアドレスをクリックして動作を確認。

LIFF アプリより別ウィンドウを開く

LIFF アプリ内で指定した URL を、LINEアプリ 内ブラウザまたは外部ブラウザで開きます。

1. index.html を以下のコードに差し替え。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My First LIFF</title>
</head>
<body>   
    <button id="openwindowbutton">ウィンドウを LINE 内で開く</button>
    <button id="openwindowexternalbutton">ウィンドウを外部ブラウザで開く</button>
    <button id="sendmessagebutton">メッセージ送信</button>
    <div id="profileinfo">
        <h2>Profile</h2>
        <div id="profilepicturediv">
        </div>
        <table border="1">
            <tr>
                <th>userId</th>
                <td id="useridprofilefield"></td>
            </tr>
            <tr>
                <th>displayName</th>
                <td id="displaynamefield"></td>
            </tr>
            <tr>
                <th>statusMessage</th>
                <td id="statusmessagefield"></td>
            </tr>
        </table>
    </div>

    <div id="liffdata">
        <h2>LIFF Data</h2>
        <table border="1">
            <tr>
                <th>language</th>
                <td id="languagefield"></td>
            </tr>
            <tr>
                <th>context.viewType</th>
                <td id="viewtypefield"></td>
            </tr>
            <tr>
                <th>context.userId</th>
                <td id="useridfield"></td>
            </tr>
            <tr>
                <th>context.utouId</th>
                <td id="utouidfield"></td>
            </tr>
            <tr>
                <th>context.roomId</th>
                <td id="roomidfield"></td>
            </tr>
            <tr>
                <th>context.groupId</th>
                <td id="groupidfield"></td>
            </tr>
        </table>
    </div>

    <!-- LIFF SDK の読み込み -->
    <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
    <script src="liff.js"></script>
</body>
</html>

2. liff.js を以下のコードに差し替え。

window.onload = function (e) {
    // init で初期化。基本情報を取得。
    // https://developers.line.me/ja/reference/liff/#initialize-liff-app
    liff.init(function (data) {
        getProfile();
        initializeApp(data);
    });

    // ウィンドウを開く
    // https://developers.line.me/ja/reference/liff/#liffopenwindow()
    document.getElementById('openwindowbutton').addEventListener('click', function () {
        liff.openWindow({
            url: 'https://line.me'
        });
    });

    document.getElementById('openwindowexternalbutton').addEventListener('click', function () {
        liff.openWindow({
            url: 'https://line.me',
            external: true
        });
    });

    // メッセージの送信
    document.getElementById('sendmessagebutton').addEventListener('click', function () {
        // https://developers.line.me/ja/reference/liff/#liffsendmessages()
        liff.sendMessages([{
            type: 'text',
            text: "テキストメッセージの送信"
        }, {
            type: 'sticker',
            packageId: '2',
            stickerId: '144'
        }]).then(function () {
            window.alert("送信完了");
        }).catch(function (error) {
            window.alert("Error sending message: " + error);
        });
    });
};

// プロファイルの取得と表示
function getProfile(){
    // https://developers.line.me/ja/reference/liff/#liffgetprofile()
    liff.getProfile().then(function (profile) {
        document.getElementById('useridprofilefield').textContent = profile.userId;
        document.getElementById('displaynamefield').textContent = profile.displayName;

        var profilePictureDiv = document.getElementById('profilepicturediv');
        if (profilePictureDiv.firstElementChild) {
            profilePictureDiv.removeChild(profilePictureDiv.firstElementChild);
        }
        var img = document.createElement('img');
        img.src = profile.pictureUrl;
        img.alt = "Profile Picture";
        img.width = 200;
        profilePictureDiv.appendChild(img);

        document.getElementById('statusmessagefield').textContent = profile.statusMessage;
    }).catch(function (error) {
        window.alert("Error getting profile: " + error);
    });
}

function initializeApp(data) {
    document.getElementById('languagefield').textContent = data.language;
    document.getElementById('viewtypefield').textContent = data.context.viewType;
    document.getElementById('useridfield').textContent = data.context.userId;
    document.getElementById('utouidfield').textContent = data.context.utouId;
    document.getElementById('roomidfield').textContent = data.context.roomId;
    document.getElementById('groupidfield').textContent = data.context.groupId;
}

3. git で変更のコミットしてプッシュ。

git commit -am "add openwindow"
git push origin master

4. LIFF アプリケーションのアドレスは変わっていないため、先ほどテストで送ったアドレスをクリックして動作を確認。

LIFF アプリを閉じる

LIFF アプリは右上の「X」をタップするか、tall/compact の場合は領域以外をタップすれば閉じますが、明示的に閉じるイベントもサポートされます。

1. index.html を以下コードに差し替え。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My First LIFF</title>
</head>
<body>           
    <button id="closewindowbutton">LIFF アプリを閉じる</button>
    <button id="openwindowbutton">ウィンドウを LINE 内で開く</button>
    <button id="openwindowexternalbutton">ウィンドウを外部ブラウザで開く</button>
    <button id="sendmessagebutton">メッセージ送信</button>
    <div id="profileinfo">
        <h2>Profile</h2>
        <div id="profilepicturediv">
        </div>
        <table border="1">
            <tr>
                <th>userId</th>
                <td id="useridprofilefield"></td>
            </tr>
            <tr>
                <th>displayName</th>
                <td id="displaynamefield"></td>
            </tr>
            <tr>
                <th>statusMessage</th>
                <td id="statusmessagefield"></td>
            </tr>
        </table>
    </div>

    <div id="liffdata">
        <h2>LIFF Data</h2>
        <table border="1">
            <tr>
                <th>language</th>
                <td id="languagefield"></td>
            </tr>
            <tr>
                <th>context.viewType</th>
                <td id="viewtypefield"></td>
            </tr>
            <tr>
                <th>context.userId</th>
                <td id="useridfield"></td>
            </tr>
            <tr>
                <th>context.utouId</th>
                <td id="utouidfield"></td>
            </tr>
            <tr>
                <th>context.roomId</th>
                <td id="roomidfield"></td>
            </tr>
            <tr>
                <th>context.groupId</th>
                <td id="groupidfield"></td>
            </tr>
        </table>
    </div>

    <!-- LIFF SDK の読み込み -->
    <script src="https://d.line-scdn.net/liff/1.0/sdk.js"></script>
    <script src="liff.js"></script>
</body>
</html>

2. liff.js を以下コードに差し替え。

window.onload = function (e) {
    // init で初期化。基本情報を取得。
    // https://developers.line.me/ja/reference/liff/#initialize-liff-app
    liff.init(function (data) {
        getProfile();
        initializeApp(data);
    });

    // LIFF アプリを閉じる
    // https://developers.line.me/ja/reference/liff/#liffclosewindow()
    document.getElementById('closewindowbutton').addEventListener('click', function () {
        liff.closeWindow();
    });

    // ウィンドウを開く
    // https://developers.line.me/ja/reference/liff/#liffopenwindow()
    document.getElementById('openwindowbutton').addEventListener('click', function () {
        liff.openWindow({
            url: 'https://line.me'
        });
    });

    document.getElementById('openwindowexternalbutton').addEventListener('click', function () {
        liff.openWindow({
            url: 'https://line.me',
            external: true
        });
    });

    // メッセージの送信
    document.getElementById('sendmessagebutton').addEventListener('click', function () {
        // https://developers.line.me/ja/reference/liff/#liffsendmessages()
        liff.sendMessages([{
            type: 'text',
            text: "テキストメッセージの送信"
        }, {
            type: 'sticker',
            packageId: '2',
            stickerId: '144'
        }]).then(function () {
            window.alert("送信完了");
        }).catch(function (error) {
            window.alert("Error sending message: " + error);
        });
    });
};

// プロファイルの取得と表示
function getProfile(){
    // https://developers.line.me/ja/reference/liff/#liffgetprofile()
    liff.getProfile().then(function (profile) {
        document.getElementById('useridprofilefield').textContent = profile.userId;
        document.getElementById('displaynamefield').textContent = profile.displayName;

        var profilePictureDiv = document.getElementById('profilepicturediv');
        if (profilePictureDiv.firstElementChild) {
            profilePictureDiv.removeChild(profilePictureDiv.firstElementChild);
        }
        var img = document.createElement('img');
        img.src = profile.pictureUrl;
        img.alt = "Profile Picture";
        img.width = 200;
        profilePictureDiv.appendChild(img);

        document.getElementById('statusmessagefield').textContent = profile.statusMessage;
    }).catch(function (error) {
        window.alert("Error getting profile: " + error);
    });
}

function initializeApp(data) {
    document.getElementById('languagefield').textContent = data.language;
    document.getElementById('viewtypefield').textContent = data.context.viewType;
    document.getElementById('useridfield').textContent = data.context.userId;
    document.getElementById('utouidfield').textContent = data.context.utouId;
    document.getElementById('roomidfield').textContent = data.context.roomId;
    document.getElementById('groupidfield').textContent = data.context.groupId;
}

3. git で変更のコミットしてプッシュ。

git commit -am "add closewindow"
git push origin master

4. LIFF アプリケーションのアドレスは変わっていないため、先ほどテストで送ったアドレスをクリックして動作を確認。

LIFF アプリの更新と削除

LIFF CLI を使えば登録済の URL やサイズを変更できるだけでなく、不要となった LIFF アプリを削除することも可能です。現状 10 個までしかアプリを登録できないため、賢く管理する必要があります。

更新

liff update <liffId> <url> <type:full|tall|compact>

削除

liff delete <liffId>
liff deleteAll

まとめ

LIFF アプリは Web 画面と Bot をシームレスに統合できる便利な機能です。是非色々試してください。

microsoft
マイクロソフトのメンバーが最新の技術情報をお届けします。Twitterアカウント(@msdevjp)やYouTubeチャンネル「クラウドデベロッパーちゃんねる」も運用中です。
https://aka.ms/MSFT-Docs-JPN
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした