LIFF App Development Guide 2026: LINE Front-end Framework with React, Next.js & TypeScript
Build LIFF (LINE Front-end Framework) apps that run inside LINE — login, profile, shareTargetPicker, openWindow external, and the HTTPS endpoint requirement. React + Next.js code, updated for LIFF v2.24+ in 2026.

#What is LIFF?
LIFF (LINE Front-end Framework) is a platform for building web applications that run inside the LINE app. LIFF apps provide a native app-like experience while using standard web technologies.
#Key Features
- Seamless Authentication: Access user's LINE profile without login forms
- Message Sending: Send messages to chats directly from your app
- Share Target Picker: Let users share content to friends and groups
- QR Code Scanner: Built-in barcode/QR scanning capability
- Native Feel: Full-screen experience inside LINE
#Use Cases
| Use Case | Example | Benefits |
|---|---|---|
| E-commerce | Product catalog, checkout | No app download needed |
| Reservations | Restaurant booking, appointments | Quick access via LINE |
| Membership | Loyalty cards, point systems | Auto-authenticated |
| Forms | Surveys, registrations | Rich UX, pre-filled data |
| Games | Mini-games, quizzes | Viral sharing features |
Learn more about LINE app development services.
#Project Setup
#Step 1: Create LIFF App in LINE Console
- Go to LINE Developers Console
- Select your provider and channel
- Navigate to LIFF tab
- Click "Add LIFF app"
- Configure:
- LIFF app name: Your app name
- Size: Full, Tall, or Compact
- Endpoint URL: Your app URL (must be HTTPS)
- Scopes: profile, openid, chat_message.write
#Step 2: Set Up React/Next.js Project
npx create-next-app@latest my-liff-app --typescript
cd my-liff-app
npm install @line/liff
#Step 3: Initialize LIFF SDK
Create a LIFF utility file:
// lib/liff.ts
import liff from '@line/liff';
const LIFF_ID = process.env.NEXT_PUBLIC_LIFF_ID!;
export async function initLiff() {
try {
await liff.init({ liffId: LIFF_ID });
console.log('LIFF initialized successfully');
return true;
} catch (error) {
console.error('LIFF initialization failed:', error);
return false;
}
}
export function isLoggedIn() {
return liff.isLoggedIn();
}
export function login() {
liff.login();
}
export function logout() {
liff.logout();
}
export async function getProfile() {
if (!liff.isLoggedIn()) return null;
return liff.getProfile();
}
export function isInClient() {
return liff.isInClient();
}
export { liff };
#Step 4: Create LIFF Provider Component
// components/LiffProvider.tsx
'use client';
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { initLiff, liff } from '@/lib/liff';
interface LiffContextType {
isReady: boolean;
isLoggedIn: boolean;
isInClient: boolean;
profile: {
userId: string;
displayName: string;
pictureUrl?: string;
} | null;
login: () => void;
logout: () => void;
}
const LiffContext = createContext<LiffContextType | null>(null);
export function LiffProvider({ children }: { children: ReactNode }) {
const [isReady, setIsReady] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [isInClient, setIsInClient] = useState(false);
const [profile, setProfile] = useState<LiffContextType['profile']>(null);
useEffect(() => {
async function init() {
const success = await initLiff();
if (success) {
setIsReady(true);
setIsLoggedIn(liff.isLoggedIn());
setIsInClient(liff.isInClient());
if (liff.isLoggedIn()) {
const userProfile = await liff.getProfile();
setProfile(userProfile);
}
}
}
init();
}, []);
const value = {
isReady,
isLoggedIn,
isInClient,
profile,
login: () => liff.login(),
logout: () => {
liff.logout();
setIsLoggedIn(false);
setProfile(null);
}
};
return (
<LiffContext.Provider value={value}>
{children}
</LiffContext.Provider>
);
}
export function useLiff() {
const context = useContext(LiffContext);
if (!context) {
throw new Error('useLiff must be used within LiffProvider');
}
return context;
}
#User Authentication
#Getting User Profile
// components/UserProfile.tsx
'use client';
import { useLiff } from './LiffProvider';
import Image from 'next/image';
export function UserProfile() {
const { isReady, isLoggedIn, profile, login, logout } = useLiff();
if (!isReady) {
return <div>Loading...</div>;
}
if (!isLoggedIn) {
return (
<button onClick={login} className="btn-primary">
Login with LINE
</button>
);
}
return (
<div className="flex items-center gap-4">
{profile?.pictureUrl && (
<Image
src={profile.pictureUrl}
alt={profile.displayName}
width={48}
height={48}
className="rounded-full"
/>
)}
<div>
<p className="font-semibold">{profile?.displayName}</p>
<button onClick={logout} className="text-sm text-gray-500">
Logout
</button>
</div>
</div>
);
}
#Access Token for Backend Verification
// Get access token for API calls
const accessToken = liff.getAccessToken();
// Send to your backend
const response = await fetch('/api/verify-user', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
Backend verification:
// pages/api/verify-user.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.headers.authorization?.replace('Bearer ', '');
// Verify token with LINE
const verifyResponse = await fetch('https://api.line.me/oauth2/v2.1/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ access_token: token! })
});
if (!verifyResponse.ok) {
return res.status(401).json({ error: 'Invalid token' });
}
const tokenInfo = await verifyResponse.json();
// tokenInfo contains: scope, expires_in, client_id
res.json({ valid: true, ...tokenInfo });
}
#Messaging Integration
#Sending Messages
// Send message to current chat
async function sendMessage(text: string) {
if (!liff.isInClient()) {
alert('This feature only works inside LINE');
return;
}
await liff.sendMessages([
{
type: 'text',
text: text
}
]);
}
// Send Flex Message
async function sendFlexMessage() {
await liff.sendMessages([
{
type: 'flex',
altText: 'Order Confirmation',
contents: {
type: 'bubble',
body: {
type: 'box',
layout: 'vertical',
contents: [
{
type: 'text',
text: 'Order Confirmed!',
weight: 'bold',
size: 'xl'
},
{
type: 'text',
text: 'Order #12345',
color: '#06C755'
}
]
}
}
}
]);
}
#Share Target Picker
Let users share content to friends or groups:
async function shareToFriends() {
if (!liff.isApiAvailable('shareTargetPicker')) {
alert('Share feature not available');
return;
}
const result = await liff.shareTargetPicker([
{
type: 'flex',
altText: 'Check out this product!',
contents: {
type: 'bubble',
hero: {
type: 'image',
url: 'https://example.com/product.jpg',
size: 'full',
aspectRatio: '20:13'
},
body: {
type: 'box',
layout: 'vertical',
contents: [
{ type: 'text', text: 'Amazing Product', weight: 'bold' },
{ type: 'text', text: '฿999', color: '#06C755' }
]
},
footer: {
type: 'box',
layout: 'vertical',
contents: [
{
type: 'button',
action: {
type: 'uri',
label: 'View Product',
uri: 'https://liff.line.me/your-liff-id/product/123'
},
style: 'primary',
color: '#06C755'
}
]
}
}
}
]);
if (result) {
console.log('Shared successfully');
}
}
#Best Practices
#1. Handle External Browser
LIFF apps can be opened in external browsers. Handle this gracefully:
function App() {
const { isReady, isInClient, isLoggedIn, login } = useLiff();
if (!isReady) return <LoadingScreen />;
// If not in LINE and not logged in, show login button
if (!isInClient && !isLoggedIn) {
return (
<div className="text-center p-8">
<h1>Welcome!</h1>
<p>Login with LINE to continue</p>
<button onClick={login} className="btn-primary">
Login with LINE
</button>
</div>
);
}
return <MainApp />;
}
#2. Responsive Design for LIFF Sizes
/* Compact: ~50% of screen height */
/* Tall: ~75% of screen height */
/* Full: 100% of screen */
.liff-container {
min-height: 100vh;
min-height: 100dvh; /* Dynamic viewport height */
}
/* Safe area for notched devices */
.liff-content {
padding-bottom: env(safe-area-inset-bottom);
}
#3. Deep Linking
Create shareable URLs that open specific pages:
// LIFF URL format: https://liff.line.me/{liffId}/{path}
const productUrl = `https://liff.line.me/${LIFF_ID}/product/${productId}`;
// In your app, handle the path
// pages/product/[id].tsx
export default function ProductPage({ params }: { params: { id: string } }) {
// Access product ID from URL
const productId = params.id;
// ...
}
#4. Error Handling
async function safeLiffOperation<T>(
operation: () => Promise<T>,
fallback: T
): Promise<T> {
try {
if (!liff.isInClient()) {
console.warn('Operation only available in LINE');
return fallback;
}
return await operation();
} catch (error) {
console.error('LIFF operation failed:', error);
return fallback;
}
}
// Usage
const result = await safeLiffOperation(
() => liff.sendMessages([{ type: 'text', text: 'Hello' }]),
undefined
);
#Deployment
#Deploy to Vercel
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel --prod
#Environment Variables
Set in Vercel dashboard or .env.local:
NEXT_PUBLIC_LIFF_ID=your-liff-id
#Update LIFF Endpoint URL
After deployment:
- Go to LINE Developers Console
- Navigate to your LIFF app
- Update Endpoint URL to your Vercel domain
- Save changes
#Testing Checklist
- Test in LINE app (iOS and Android)
- Test in external browser
- Test login/logout flow
- Test message sending (if applicable)
- Test share target picker (if applicable)
- Test on different screen sizes
- Test error scenarios
#Conclusion
LIFF enables powerful integrations between your web apps and LINE. Key takeaways:
- Initialize LIFF early in your app lifecycle
- Handle both in-client and external browser scenarios
- Use Share Target Picker for viral features
- Always verify access tokens on your backend
- Test thoroughly on actual devices
Ready to build your LIFF app?
Try LineBot.pro for rapid LIFF development with our templates and tools. Build LINE mini-apps faster with our pre-built components and infrastructure.
Related resources:
Related Services
About LineBot.pro
LineBot.pro is a LINE automation platform for building rich menus, chatbots, and broadcast campaigns on LINE Official Accounts. Our team writes these guides based on hands-on work with the LINE Messaging API.
Contact usRelated Articles

@line/bot-sdk Express Middleware: Complete Example with replyMessage & validateSignature
Working code example for the @line/bot-sdk Express middleware: webhook signature validation, replyMessage with ReplyMessageResponse.sentMessages, error handling, and TypeScript types.
Read More
LINE Messaging API 2026 Updates: New Features, Pricing & Changes
Comprehensive 2026 update reference for the LINE Messaging API: new features, pricing changes (free tier monthly messages), Rich Menu limits, Mini App updates, Thailand & Taiwan-specific pricing tiers.
Read More
LINE Chatbot Development Tutorial: Build Your First Bot Step by Step
Complete guide to building LINE chatbots from scratch. Learn Messaging API architecture, webhook setup, message handling, AI integration, and deployment with Node.js and Python examples.
Read MoreReady to Automate Your LINE Business?
Start automating your LINE communications with LineBot.pro today.