DEV Community

Cover image for šŸš€ Your First Telegram Mini App: Fast Deployment with Next.js, Vercel and TelegramĀ SDK
Diana Agliamutdinova
Diana Agliamutdinova

Posted on

šŸš€ Your First Telegram Mini App: Fast Deployment with Next.js, Vercel and TelegramĀ SDK

In this guide, I’ll show you how to build a Telegram Mini App for your bot:

  • āš™ļø Create a Next.js project (BFFā€Šā€”ā€Šbackend for frontend)
  • ā˜ļø Deploy your app on Vercel
  • šŸ¤– Register your Mini App with BotFather
  • šŸ” Authorize users through Telegram’s Web App initData
  • šŸ’¬ Enable core features: Message sharing, Styling tips, Back button initialization
  • šŸ’” Use Telegram Popups

By the end of this guide, you’ll have a working Mini App inside the Telegram environment that is production-ready on Vercel.

🧱 1. Project set up

Project schema

šŸ›  What You’llĀ Need

  1. Create a bot with @botfather and get your BOT_TOKEN

Bot token in the telegram bots page in @BotFather interface

  1. Create your Next.js app

Open your terminal and run the following command. Follow the interactive setup:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

šŸ—ļø Using Next.js as a BFF (Backend-for-Frontend) with App Router

How files and directories located in Next.js project

  1. Push your project to GitHub

Go to GitHub, create a new repository, and follow the Quick setup instructions to push your local project to the repo.

  1. Deploy your app to Vercel

Telegram doesn’t host your websiteā€Šā€”ā€Šyour Mini App must be hosted somewhere publicly accessible.

  • Create an account on Vercel
  • Link your GitHub account
  • Select your newly created repository
  • Follow the prompts to deploy your project

šŸ’” By this point, your app should be live and hosted on the webā€Šā€”ā€Šready to be linked with your Telegram bot. Also your app will be redeployed on every commit to the remote repository.


šŸŖ„ 2. Set Up a MiniĀ App

āœ… Method 1: Using /newapp Command

  1. Send /newapp to BotFather
  2. Choose your bot (e.g., @YourAppBot)
  3. Add a title and description for your Web App
  4. Provide the URL of your hosted Mini App (e.g., your Vercel app URL)

āš™ļø Method 2: Via BotFather’s Web Interface (Mini App Panel)

  1. Open BotFather’s Mini App panel
  2. Go to My Bots and select the bot you want to update
  3. Navigate to:Ā Settings → Mini Apps
  4. Click on Main App, enable it, and paste in your Mini App’s URL
  5. Optionally, create a Direct Linkā€Šā€”ā€Šthis is a link that directly opens your Mini App from Telegram( e.g., t.me//your_bot_name/…)

Once set, the

ā€œOpenā€ button

on the right should launch your Mini App inside Telegram. šŸŽ‰

Link


šŸ” 3. Authorize a user with the Telegram Mini AppĀ SDK

  1. Frontend: Get initData from SDK and send it to your backend with API requests.
  2. Backend: Validate initData using your bot token.
  3. If valid: The user is authorized! You can trust the user info in initData.

Install the SDK:

npm install @telegram-apps/sdk-react
Enter fullscreen mode Exit fullscreen mode

Since Telegram injects the Telegram object only in the browser, you must use the SDK inside a client-only componentā€Šā€”ā€Šotherwise, you’ll run into SSR issues.

Use next/dynamic to import your Telegram logic with SSR disabled:

const TelegramInit = dynamic(() => import('../telegram/client/TelegramInit'), {
  ssr: false,
});
Enter fullscreen mode Exit fullscreen mode

Save rawInitData to use it for autorization. Send it to your backend to autorize api requests.

import { useRawInitData } from '@telegram-apps/sdk-react';

export default function TelegramInit() {
  const rawInitData = useRawInitData();
  useEffect(() => {
    if (rawInitdata) {
      localStorage.setItem('token', rawInitdata);
    }
  }, [rawInitdata]);
}
Enter fullscreen mode Exit fullscreen mode
const res = await fetch(url, {
  method: method,
  headers: {
  'Content-Type': 'application/json',
  Authorization: `Bearer ${localStorage.getItem('token')}`,
  },
  body: body ? JSON.stringify(body) : undefined,
});
Enter fullscreen mode Exit fullscreen mode

Also you can get usefull user information from telegram(first_name, last_name, is_bot, photo_url, …)

import { parse } from '@telegram-apps/init-data-node/web';
const initData = localStorage.getItem('token');
    if (initData) {
      try {
        const parsedData = parse(initData);
        if (parsedData.user) {
          createUserInDB(parsedData.user);
          setUser(parsedData.user);
        }
      } catch (error) {
        console.error('Failed to parse user data:', error);
      }
Enter fullscreen mode Exit fullscreen mode

Validate initdata on the server using the Telegram Mini Apps SDK:

  • token is initdata you get from request headers
  • BOT_TOKEN is your bot token from BotFather:
import { isValid } from '@telegram-apps/init-data-node/web';
...
const isAuthorized = isValid(token, process.env.BOT_TOKEN!);
Enter fullscreen mode Exit fullscreen mode

If valid, you can safely use the user info for DB operations.

šŸ” Don’t Forget: Set Your BOT_TOKEN

To securely use your bot’s token in both development and production environments:

🧪 1. Add it to your local environment

Create aĀ .env.local file in the root of your Next.js project and add:

BOT_TOKEN=your_telegram_bot_token_here
Enter fullscreen mode Exit fullscreen mode

This keeps your token out of version control and safe from exposure.

ā˜ļø 2. Add it to Vercel Environment Variables

To make sure your bot works in production:

  1. Go to your project dashboard on Vercel
  2. Open the Settings tab
  3. Click on Environment Variables
  4. Add a new variable:
  5. Name: BOT_TOKEN
  6. Value: your actual bot token

Make sure to

redeploy

your project after saving the variable so it’s available at runtime.


šŸ“¤ 4. Sharing Messages from MiniĀ App

  1. Frontend: User clicks ā€œShareā€ → send data to /api/share
  2. Backend: Call savePreparedInlineMessage → get message ID
  3. Frontend: Call shareMessage({ id }) with the message ID

Frontend:

import { shareMessage } from '@telegram-apps/sdk-react';
// send info to the backend
const response = await shareTodo(todoId, userId, title, description);
if (response.ok) {
// get event id
  const data = await response.json();
  if (shareMessage.isAvailable()) {
// share message
    await shareMessage(data.id);
  }
}
Enter fullscreen mode Exit fullscreen mode

Backend:

On your backend, receive the data and call the Telegram Bot API’s savePreparedInlineMessage method.

const shareMessage = {
  user_id: userId,
  result: {
    type: 'photo',
    id: `todo-${todoId}`,
    photo_url: 'https://example.com/photo.jpg',
    thumbnail_url: 'https://example.com/thumb.jpg',
    title: `šŸ“ ${todoTitle}`,
    description: todoDescription || 'A task from my Mini App',
    caption: `**${todoTitle}**\n\n${todoDescription || ''}`,
    parse_mode: 'Markdown',
  },
};
Enter fullscreen mode Exit fullscreen mode
// send reuest to Telegram Bot API
const result = await savePreparedInlineMessage(shareMessage);
return NextResponse.json({ id: result.id });
Enter fullscreen mode Exit fullscreen mode

šŸ“± 5. Telegram-Specific Features

šŸ”™ Back Button Behavior

If your Mini App has multiple pages or routing, you might want to enable back navigation. By default, the Telegram back button doesn’t behave like a browser back buttonā€Šā€”ā€Šit needs to be explicitly initialized and controlled.

On pages where you want the back button visible, you should show it; on others, hide it. Otherwise, pressing back will close the Mini App instead of navigating back.

āŒ Closing Behavior

If you want to remind users to save their data before closing the app, you can enable a confirmation prompt on close. This helps prevent accidental loss of unsaved changes by asking the user for confirmation before exiting.

šŸŽØ Mini App Component

To customize your Mini Appā€Šā€”ā€Šlike setting the header or background colorsā€Šā€”ā€Šor to use status methods like checking if the Mini App is active, you first need to initialize (mount) the Mini App component. Only after mounting can you safely call these methods.

'use client';

import { useEffect } from 'react';
import {
  backButton,
  closingBehavior,
  init,
  miniApp,
  useRawInitData,
} from '@telegram-apps/sdk-react';

export default function TelegramInit() {
  const rawInitdata = useRawInitData();

  useEffect(() => {
    try {
      init(); // Initialize Telegram SDK
      if (backButton.mount.isAvailable()) {
        backButton.mount();
        backButton.onClick(() => {
          if (backButton.isMounted()) {
            backButton.hide();
          }
          window.history.back();
        });
      }
      console.log('TelegramProvider - init()');
      if (miniApp.mountSync.isAvailable()) {
        miniApp.mountSync();
      }
      if (closingBehavior.mount.isAvailable()) {
        closingBehavior.mount();
        closingBehavior.isMounted(); // true
      }
      if (closingBehavior.enableConfirmation.isAvailable()) {
        closingBehavior.enableConfirmation();
        closingBehavior.isConfirmationEnabled(); // true
      }
    } catch (err) {
      console.error('Telegram SDK init failed:', err);
      const cleanUrl = window.location.origin + window.location.pathname;
      window.history.replaceState({}, '', cleanUrl);
    }

    return () => {
      if (backButton.isMounted()) {
        backButton.unmount();
      }
    };
  }, []);
Enter fullscreen mode Exit fullscreen mode

šŸ’¬ 6. TelegramĀ Popups

Basically you create a popup through popup.show with buttons you need.

Then you wait for buttonId which popup.show returns(which button user clicked) and do accordingly.

import { popup } from '@telegram-apps/sdk-react';

export const showTelegramPopup = async (
  title: string,
  message: string,
  buttonText: string[]
): Promise<boolean> => {

  if (popup) {
    const promise = popup.show({
      title: title,
      message: message,
      buttons: buttonText.map((text, index) => ({
        id: `button_${index}`,
        type: 'default',
        text: text,
      })),
    });
    // popup.isOpened() -> true
    const buttonId = await promise;
    if (buttonId === 'button_0') {
      return true;
      // do something
    } else if (buttonId === 'button_1') {
      return false;
      // do something else
    }
    // If buttonId is neither 'ok' nor 'cancel', return false by default
    return false;
  }
  // If popup is not available, return false
  return false;
};
Enter fullscreen mode Exit fullscreen mode

šŸ”— Links

🧠 Final Thoughts

Telegram Mini Apps are a powerful way to create native-like experiences directly within Telegram. With the help of Vercel, you can go from idea to live app in no time.

šŸ”„ Teaser

There’s more to exploreā€Šā€”ā€Šlike handling payments with Telegram Starsā€Šā€”ā€Šbut it didn’t quite fit in this article.

If you’d like me to cover it next, please like this post or drop a comment below letting me know!

Your feedback helps me prioritize what to write about next.

This article was originally published on Medium.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.