telegram-mini-apps-react について
この Skill は、`@tma.js/sdk-react` で Telegram Mini Apps を開発する React 開発者向けのガイドです。Telegram の UI、ユーザーデータと起動パラメーター、テーマ、ビューポート、Telegram 外でのローカル開発を扱います。
主な機能
- 01React SDK の初期化と利用
- 02Telegram ネイティブ UI の統合
- 03初期データと起動パラメーターの取得
- 04テーマとビューポートの CSS 変数設定
- 05ローカル開発用 Telegram 環境のモック
- 06ディープリンクとプラットフォーム機能の確認
活用シーン
- 01Telegram 向け React Mini App の開発
- 02Telegram のテーマとセーフエリアに合わせたレイアウト調整
- 03Telegram 外でのアプリ動作テスト
- 04起動パラメーターを使ったアプリ内遷移
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nailorsh/ agents_utils --skill telegram-mini-apps-reactインストール後、Claude Code で/telegram-mini-apps-reactを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/back-button-modal.tsxpage-component.tsxuser-data.tsxreferences/deeplink.mdinit.mdmock-env.md詳細
React を使用した Telegram Mini Apps
このスキルでは、React と @tma.js/sdk-react パッケージを使用して Telegram Mini Apps を構築するための包括的なガイダンスを提供します。
概要
Telegram Mini Apps は、Telegram の WebView 内に表示されるウェブアプリケーションです。Telegram のネイティブ UI コンポーネント(戻るボタン、メインボタン)と連携し、ユーザーデータ、テーマパラメーター、プラットフォーム固有の機能を利用できます。
主な概念:
- Mini Apps は Telegram ボットのアドオンです
- Telegram の WebView 内で動作します
- イベントとメソッドを介して Telegram クライアントと通信します
- 起動パラメーター、初期化データ、テーマ設定を利用できます
クイックスタート
1. インストール
# For React projects, install the React-specific package
pnpm i @tma.js/sdk-react
# DO NOT install both @tma.js/sdk and @tma.js/sdk-react - this causes bugs!
重要:
@tma.js/sdk-reactパッケージは@tma.js/sdkを完全に再エクスポートするため、個別にインストールする必要はありません。
2. 新しいプロジェクトの作成(任意)
pnpm dlx @tma.js/create-mini-app@latest
# or
npx @tma.js/create-mini-app@latest
この CLI は、適切な設定を備えたプロジェクト一式を生成します。
プロジェクト構成
一般的な Telegram Mini App の React プロジェクト構成:
src/
├── main.tsx # Entry point - SDK initialization
├── init.ts # SDK configuration and component mounting
├── mockEnv.ts # Development environment mocking
├── App.tsx # Main React app with routing
├── components/
│ ├── Page.tsx # Page wrapper with back button handling
│ └── EnvUnsupported.tsx # Fallback for non-TG environments
├── hooks/
│ └── useDeeplink.ts # Deep linking handler
└── services/
└── analytics.ts # Analytics with user data
主な概念
SDK の初期化
機能を使用する前に SDK を初期化する必要があります。詳しい実装については、references/init.md を参照してください。
import {
init as initSDK,
setDebug,
themeParams,
miniApp,
viewport,
backButton,
swipeBehavior,
initData
} from '@tma.js/sdk-react';
export async function init(options: {
debug: boolean;
eruda: boolean;
mockForMacOS: boolean;
}): Promise<void> {
// Enable debug mode for development
setDebug(options.debug);
// Initialize the SDK (REQUIRED before using any features)
initSDK();
// Mount components you'll use in the app
backButton.mount.ifAvailable();
initData.restore();
// Configure swipe behavior
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical();
}
// Setup Mini App theming
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Binds theme to CSS variables
}
// Configure viewport
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars();
viewport.requestFullscreen();
});
}
}
エントリーポイント(main.tsx)
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { retrieveLaunchParams } from '@tma.js/sdk-react';
import { init } from './init';
import App from "./App";
import { EnvUnsupported } from "./components/EnvUnsupported";
// Mock environment for local development
import './mockEnv';
const root = ReactDOM.createRoot(document.getElementById('root')!);
try {
const launchParams = retrieveLaunchParams();
const { tgWebAppPlatform: platform } = launchParams;
const debug = (launchParams.tgWebAppStartParam || '').includes('debug')
|| import.meta.env.DEV;
await init({
debug,
eruda: debug && ['ios', 'android'].includes(platform),
mockForMacOS: platform === 'macos',
}).then(() => {
root.render(
<StrictMode>
<App/>
</StrictMode>,
);
});
} catch (e) {
// Show fallback UI when not in Telegram
root.render(<EnvUnsupported/>);
}
戻るボタンの使用
戻るボタンは、ヘッダーに表示される Telegram のネイティブ UI 要素です。
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { backButton, miniApp } from '@tma.js/sdk-react';
export function Page({ children, back = true }) {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
if (back) {
backButton.show();
// onClick returns a cleanup function
return backButton.onClick(() => {
const isDeeplink = location.state?.fromDeeplink;
const isFirstPage = !window.history.state || window.history.state.idx === 0;
if (isDeeplink || isFirstPage) {
miniApp.close(); // Close the Mini App
} else {
navigate(-1); // Go back in history
}
});
}
backButton.hide();
}, [back, navigate, location]);
return <>{children}</>;
}
シグナルの使用(useSignal フック)
シグナルは、自動的に更新されるリアクティブな値です。React でシグナルを購読するには、useSignal を使用します:
import { useEffect } from 'react';
import { backButton, useSignal } from '@tma.js/sdk-react';
function BackButtonStatus() {
const isVisible = useSignal(backButton.isVisible);
useEffect(() => {
console.log('Back button is', isVisible ? 'visible' : 'hidden');
}, [isVisible]);
return null;
}
初期化データの取得(ユーザー情報)
初期化データにはユーザー情報が含まれており、認証に使用できます:
import { initData } from '@tma.js/sdk-react';
function getUserId(): number | undefined {
try {
const user = initData.user();
return user?.id;
} catch (e) {
return undefined;
}
}
// Get start parameter (for deep linking)
const startParam = initData.startParam();
起動パラメーター
起動パラメーターには、プラットフォーム情報、テーマ、アプリデータが含まれます:
import { retrieveLaunchParams, useLaunchParams } from '@tma.js/sdk-react';
// In component
function Component() {
const launchParams = useLaunchParams();
// launchParams.tgWebAppPlatform - 'ios', 'android', 'macos', 'tdesktop', 'web', 'weba'
// launchParams.tgWebAppVersion - SDK version supported by client
// launchParams.tgWebAppData - init data
// launchParams.tgWebAppThemeParams - theme colors
// launchParams.tgWebAppStartParam - custom start parameter
}
// Outside component
const launchParams = retrieveLaunchParams();
テーマ設定
テーマパラメーターは Telegram によって自動的に提供されます。CSS 変数にバインドします:
import { themeParams, miniApp } from '@tma.js/sdk-react';
// During initialization
if (miniApp.mount.isAvailable()) {
themeParams.mount();
miniApp.mount();
themeParams.bindCssVars(); // Creates CSS variables like --tg-theme-bg-color
}
バインド後に使用できる CSS 変数:
--tg-theme-bg-color--tg-theme-text-color--tg-theme-hint-color--tg-theme-link-color--tg-theme-button-color--tg-theme-button-text-color--tg-theme-secondary-bg-color--tg-theme-header-bg-color--tg-theme-accent-text-color--tg-theme-section-bg-color--tg-theme-section-header-text-color--tg-theme-subtitle-text-color--tg-theme-destructive-text-color
ビューポートとセーフエリア
適切なレイアウトになるよう、ビューポートとセーフエリアを処理します:
import { viewport } from '@tma.js/sdk-react';
if (viewport.mount.isAvailable()) {
viewport.mount().then(() => {
viewport.bindCssVars(); // Binds viewport dimensions to CSS
viewport.requestFullscreen(); // Request fullscreen mode
});
}
使用できる CSS 変数:
/* Safe area insets */
padding-top: var(--tg-viewport-safe-area-inset-top, 0);
padding-bottom: var(--tg-viewport-safe-area-inset-bottom, 0);
/* Content safe area (for notch, etc.) */
padding-top: var(--tg-viewport-content-safe-area-inset-top, 0);
/* Viewport dimensions */
height: var(--tg-viewport-height);
width: var(--tg-viewport-width);
CSS での使用方法:
.header {
padding-top: max(2rem, calc(var(--tg-viewport-content-safe-area-inset-top, 0) + var(--tg-viewport-safe-area-inset-top, 0)));
}
.footer {
padding-bottom: calc(1rem + var(--tg-viewport-safe-area-inset-bottom, 0));
}
開発環境のモック
Telegram 外でローカル開発する場合は、環境をモックします。詳しくは references/mock-env.md を参照してください。
import { emitEvent, isTMA, mockTelegramEnv } from '@tma.js/sdk-react';
if (import.meta.env.DEV) {
if (!await isTMA('complete')) {
const themeParams = {
accent_text_color: '#6ab2f2',
bg_color: '#17212b',
button_color: '#5288c1',
button_text_color: '#ffffff',
destructive_text_color: '#ec3942',
header_bg_color: '#17212b',
hint_color: '#708499',
link_color: '#6ab3f3',
secondary_bg_color: '#232e3c',
section_bg_color: '#17212b',
section_header_text_color: '#6ab3f3',
subtitle_text_color: '#708499',
text_color: '#f5f5f5',
};
mockTelegramEnv({
onEvent(e) {
if (e.name === 'web_app_request_theme') {
return emitEvent('theme_changed', { theme_params: themeParams });
}
if (e.name === 'web_app_request_viewport') {
return emitEvent('viewport_changed', {
height: window.innerHeight,
width: window.innerWidth,
is_expanded: true,
is_state_stable: true,
});
}
if (e.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
},
launchParams: new URLSearchParams([
['tgWebAppThemeParams', JSON.stringify(themeParams)],
['tgWebAppData', new URLSearchParams([
['auth_date', (Date.now() / 1000 | 0).toString()],
['hash', 'mock-hash'],
['signature', 'mock-signature'],
['user', JSON.stringify({ id: 1, first_name: 'Developer' })],
]).toString()],
['tgWebAppVersion', '8.4'],
['tgWebAppPlatform', 'tdesktop'],
]),
});
console.info('⚠️ Running in mocked Telegram environment');
}
}
ディープリンク
ディープリンク用の start パラメーターを処理します。詳しくは references/deeplink.md を参照してください。
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { initData } from "@tma.js/sdk-react";
export function useDeeplink() {
const navigate = useNavigate();
const processedRef = useRef(false);
useEffect(() => {
if (processedRef.current) return;
const startParam = initData.startParam();
if (!startParam) return;
processedRef.current = true;
try {
// startParam is base64url encoded
const base64 = startParam.replace(/-/g, '+').replace(/_/g, '/');
const decoded = atob(base64);
const params = new URLSearchParams(decoded);
const route = params.get('route');
if (route) {
navigate(route, { replace: true, state: { fromDeeplink: true } });
}
} catch (e) {
console.error("Failed to parse startParam:", e);
}
}, [navigate]);
}
ベストプラクティス
1. 必ず利用可能か確認する
メソッドを使用する前に、利用可能かどうかを確認します:
import { backButton } from '@tma.js/sdk-react';
// Option 1: Check before calling
if (backButton.show.isAvailable()) {
backButton.show();
}
// Option 2: Call only if available (safer, no-op if unavailable)
backButton.show.ifAvailable();
// Option 3: Mount only if available
backButton.mount.ifAvailable();
2. 使用前にコンポーネントをマウントする
メソッドを使用する前に、コンポーネントをマウントする必要があります:
// ❌ Wrong - will throw error
backButton.show();
// ✅ Correct
backButton.mount();
backButton.show();
3. macOS の不具合に対処する
Telegram for macOS には既知の問題があります:
if (platform === 'macos') {
mockTelegramEnv({
onEvent(event, next) {
if (event.name === 'web_app_request_theme') {
const tp = themeParams.state() || retrieveLaunchParams().tgWebAppThemeParams;
return emitEvent('theme_changed', { theme_params: tp });
}
if (event.name === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
}
next();
},
});
}
4. SDK を重複してインストールしない
@tma.js/sdk と @tma.js/sdk-react の両方をインストールしないでください:
// ❌ Wrong - causes bugs
{
"dependencies": {
"@tma.js/sdk": "^3.0.0",
"@tma.js/sdk-react": "^3.0.8"
}
}
// ✅ Correct - only the React package
{
"dependencies": {
"@tma.js/sdk-react": "^3.0.8"
}
}
5. 必要に応じてスワイプを無効にする
誤操作によるナビゲーションを防ぎます:
if (swipeBehavior.isSupported()) {
swipeBehavior.mount();
swipeBehavior.disableVertical(); // Prevents swipe-to-close
}
初期化データをサーバーに送信する
認証のため、初期化データをサーバーに送信します:
import { retrieveRawInitData } from '@tma.js/sdk-react';
const initDataRaw = retrieveRawInitData();
fetch('https://api.example.com/auth', {
method: 'POST',
headers: {
Authorization: `tma ${initDataRaw}`,
},
});
サーバー側での検証:
- Node.js では
@tma.js/init-data-nodeを使用します - ボットトークンを使ってハッシュを検証します
- 検証せずに初期化データを信頼しないでください
対応プラットフォーム
Mini Apps は以下のプラットフォームで動作します:
- android - Telegram for Android
- ios - Telegram for iOS
- macos - Telegram for macOS(一部不具合があります)
- tdesktop - Telegram Desktop
- weba - Telegram Web A
- web - Telegram Web K
よくある問題
SDK が初期化されていないというエラー
SDK の機能を使用する前に init() を呼び出してください。
コンポーネントがマウントされていないというエラー
メソッドを呼び出す前にコンポーネントをマウントしてください:
backButton.mount();
backButton.show();
メソッドが利用できないというエラー
呼び出す前に利用可能かどうかを確認してください:
if (backButton.show.isAvailable()) {
backButton.show();
}
Telegram 以外でアプリがクラッシュする
開発中は環境をモックし、フォールバック UI を用意してください。
追加リソース
関連項目
- references/init.md - 初期化の完全な例
- references/mock-env.md - 環境のモック
- references/deeplink.md - ディープリンクの実装
- examples/page-component.tsx - 戻るボタン付きのページラッパー