Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Miro with V0

To integrate Miro with V0 by Vercel, use the Miro REST API through a Next.js API route. V0 generates the dashboard UI; your server-side route authenticates with a Miro OAuth token or API key and fetches boards, items, and widgets. Display real-time Miro board data or embed boards directly in your app using Miro's embeddable iframe URLs.

What you'll learn

  • How to authenticate with the Miro REST API from a Next.js API route
  • How to fetch a list of Miro boards and display them in a V0-generated UI
  • How to read board items (sticky notes, shapes, connectors) via the Miro API
  • How to embed a Miro board using an iframe in a Next.js page
  • How to store your Miro OAuth token securely in Vercel environment variables
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read25 minutesProductivityApril 2026RapidDev Engineering Team
TL;DR

To integrate Miro with V0 by Vercel, use the Miro REST API through a Next.js API route. V0 generates the dashboard UI; your server-side route authenticates with a Miro OAuth token or API key and fetches boards, items, and widgets. Display real-time Miro board data or embed boards directly in your app using Miro's embeddable iframe URLs.

Display Miro Board Data and Embed Boards in Your V0 Next.js App

Miro's REST API gives your V0-generated Next.js app access to everything on a Miro board — sticky notes, shapes, text items, connectors, frames, and the full board structure. This means you can build project dashboards that pull live data from Miro boards, display sprint retrospective notes alongside other project metrics, or embed Miro canvases directly into your app for teams to view without switching tools.

For V0 developers, the integration pattern follows the standard external-API approach: V0 generates the React components (a board listing page, an item display grid, an embedded viewer), and a Next.js API route handles Miro authentication and data fetching on the server. The Miro OAuth access token never touches the browser — it stays in Vercel environment variables and is only used in your server-side route handler.

Miro also supports board embedding via a simple iframe URL: each Miro board has an embed link that can be dropped into a Next.js page without any API calls. This makes it easy to give app users a read-only view of a Miro board as part of a larger dashboard. The full REST API is needed when you want to extract board data programmatically — reading specific sticky note text, counting items by type, or pulling data into a database.

Integration method

Next.js API Route

Miro connects to V0-generated Next.js apps through server-side API routes using Miro's REST API. An OAuth 2.0 access token authenticates requests to read boards, items, sticky notes, and widgets. The V0-generated frontend displays Miro data fetched from your API route, keeping the Miro token safely on the server.

Prerequisites

  • A V0 account at v0.dev with a Next.js project created
  • A Miro account at miro.com (free plan works for development)
  • A Miro developer app created at developers.miro.com with a client ID and client secret
  • A Miro OAuth access token with read:boards and read:board:item scopes
  • A Vercel account connected to your V0 project for deployment

Step-by-step guide

1

Create a Miro Developer App and Get an Access Token

Go to developers.miro.com and sign in with your Miro account. Click 'Create new app' and give it a name like 'My V0 Dashboard'. Under 'App Credentials' you will find your Client ID and Client Secret — save these for later. For initial development, the fastest path is creating a static access token: go to your app settings, navigate to 'OAuth & Permissions', and under 'Redirect URI for OAuth' add your Vercel deployment URL (e.g., https://your-app.vercel.app/api/miro/callback). For development testing without full OAuth, Miro provides a way to generate a user access token directly from the developer portal. Go to your app page → 'OAuth & Permissions' → scroll to 'Access Token' and click 'Get token' — this gives you a personal access token tied to your account for testing. Copy this token, as it goes into your Vercel environment variables as MIRO_ACCESS_TOKEN. The scopes you need depend on what you want to access. For reading boards and items, add: boards:read and board:item:read. If you want to embed boards, no API token is needed at all — just the board ID from the Miro URL. For production apps serving multiple users, implement the full OAuth 2.0 flow so each user authorizes the app with their own Miro account. For a single-team internal tool, a single team access token from your Miro workspace admin is sufficient.

V0 Prompt

Create a settings page with a Miro API connection form. Show fields for 'Miro Access Token' and 'Default Board ID'. Include a 'Test Connection' button that calls /api/miro/test and displays either a green 'Connected to Miro — found X boards' success message or a red error message. Add a link to developers.miro.com for getting credentials.

Paste this in V0 chat

Pro tip: For single-team internal tools, a workspace token is simpler than user-level OAuth. Ask your Miro workspace admin to generate a token in the workspace settings. This token has access to all boards the workspace can see.

Expected result: You have a Miro access token saved. You can see your Miro boards at https://miro.com/app/dashboard and have noted the board IDs you want to integrate (visible in the URL when a board is open).

2

Create the Miro Boards API Route

Create app/api/miro/boards/route.ts to fetch a list of Miro boards from the Miro REST API. The Miro REST API base URL is https://api.miro.com/v2. All requests require an Authorization header with Bearer plus your access token. The boards endpoint returns a paginated list of boards with their ID, name, thumbnail, description, team, and modification dates. The API route uses process.env.MIRO_ACCESS_TOKEN for authentication — never use NEXT_PUBLIC_ prefix here since this is a secret server-side credential. The route fetches from Miro's boards API and returns the data to your frontend component. Miro's API returns results with a cursor field for pagination; for initial implementation, fetch the first page (default 20 boards) and add pagination later. For better performance, consider caching the boards list since it does not change frequently. Next.js route handlers support the cache option: add next: { revalidate: 60 } to the fetch call to cache for 60 seconds. This reduces Miro API calls and keeps your app snappy even under load. Miro's rate limits are generous (1000 requests per minute per token) but caching is still good practice. Note a key V0 limitation: V0 generates the UI components and the API route structure, but it cannot authenticate with Miro to test that the generated code actually works. Always test the route manually after deployment by checking the Vercel Function Logs for any authentication or network errors.

V0 Prompt

Create a boards listing page that fetches from /api/miro/boards and displays each board as a card with: board name as heading, description text (truncated to 2 lines), a thumbnail image using Next.js Image, last modified date in relative format (e.g., '2 days ago'), and two action buttons: 'View Items' and 'Embed Board'. Add a grid layout with 3 columns on desktop, 2 on tablet, 1 on mobile. Show 6 skeleton cards while loading.

Paste this in V0 chat

app/api/miro/boards/route.ts
1import { NextResponse } from 'next/server';
2
3const MIRO_API_BASE = 'https://api.miro.com/v2';
4
5export async function GET() {
6 const token = process.env.MIRO_ACCESS_TOKEN;
7 if (!token) {
8 return NextResponse.json(
9 { error: 'MIRO_ACCESS_TOKEN environment variable not set' },
10 { status: 500 }
11 );
12 }
13
14 try {
15 const response = await fetch(`${MIRO_API_BASE}/boards?limit=50`, {
16 headers: {
17 Authorization: `Bearer ${token}`,
18 Accept: 'application/json',
19 },
20 next: { revalidate: 60 }, // Cache for 60 seconds
21 });
22
23 if (!response.ok) {
24 const error = await response.json();
25 return NextResponse.json(
26 { error: error.message || 'Miro API request failed', status: response.status },
27 { status: response.status }
28 );
29 }
30
31 const data = await response.json();
32 return NextResponse.json({
33 boards: data.data,
34 total: data.total,
35 cursor: data.cursor,
36 });
37 } catch (error) {
38 console.error('Miro API error:', error);
39 return NextResponse.json(
40 { error: 'Failed to fetch Miro boards' },
41 { status: 500 }
42 );
43 }
44}

Pro tip: Each Miro board has a unique ID visible in the board URL: https://miro.com/app/board/uXjVM_boardId=/. You can hardcode specific board IDs for a focused integration rather than fetching all accessible boards.

Expected result: Calling /api/miro/boards returns a JSON array of your Miro boards with name, ID, thumbnail URL, and modification dates. The frontend displays board cards with real data.

3

Create the Board Items API Route

Create app/api/miro/boards/[boardId]/items/route.ts to fetch items (sticky notes, shapes, text, frames, images) from a specific Miro board. This dynamic route accepts a boardId path parameter and an optional type query parameter to filter by item type. Miro board items are the individual elements on the canvas. The items endpoint at /v2/boards/{boardId}/items returns all item types; you can filter by passing type=sticky_note, type=shape, type=text, or type=frame. Each item has a data field with the content (for sticky notes, this is the text), a style field with color and formatting, and a position field with x/y coordinates on the canvas. For sticky notes specifically, the data.content field contains the HTML-formatted text (Miro uses simple HTML for styling within sticky notes). Strip HTML tags if you want plain text — a simple regex or DOMParser approach works. For shapes and connectors, the data field contains geometry info. For text items, data.content has the text. Note: Miro's API requires the boardId to be URL-encoded if it contains special characters. The dynamic route segment [boardId] in Next.js handles this automatically through params. However, Miro board IDs sometimes end with = signs which are valid base64 characters — ensure your API calls encode these properly when constructing the URL.

V0 Prompt

Build a board items viewer page at /boards/[boardId]/items that calls /api/miro/boards/[boardId]/items with a type filter dropdown (All Items, Sticky Notes, Shapes, Text). Display items in a masonry grid where sticky notes show their background color and text content. Add a search bar that filters items client-side by text content. Show item count per type in a summary bar at the top.

Paste this in V0 chat

app/api/miro/boards/[boardId]/items/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2
3const MIRO_API_BASE = 'https://api.miro.com/v2';
4
5interface RouteParams {
6 params: { boardId: string };
7}
8
9export async function GET(request: NextRequest, { params }: RouteParams) {
10 const token = process.env.MIRO_ACCESS_TOKEN;
11 if (!token) {
12 return NextResponse.json(
13 { error: 'MIRO_ACCESS_TOKEN not configured' },
14 { status: 500 }
15 );
16 }
17
18 const { searchParams } = new URL(request.url);
19 const type = searchParams.get('type');
20 const limit = searchParams.get('limit') || '50';
21 const boardId = encodeURIComponent(params.boardId);
22
23 const typeParam = type ? `&type=${type}` : '';
24 const url = `${MIRO_API_BASE}/boards/${boardId}/items?limit=${limit}${typeParam}`;
25
26 try {
27 const response = await fetch(url, {
28 headers: {
29 Authorization: `Bearer ${token}`,
30 Accept: 'application/json',
31 },
32 });
33
34 if (!response.ok) {
35 const error = await response.json();
36 return NextResponse.json(
37 { error: error.message || 'Failed to fetch board items' },
38 { status: response.status }
39 );
40 }
41
42 const data = await response.json();
43
44 // Clean HTML from sticky note content
45 const items = data.data.map((item: { type: string; data?: { content?: string }; [key: string]: unknown }) => ({
46 ...item,
47 data: item.data
48 ? {
49 ...item.data,
50 content: item.data.content
51 ? item.data.content.replace(/<[^>]*>/g, '')
52 : '',
53 }
54 : item.data,
55 }));
56
57 return NextResponse.json({
58 items,
59 total: data.total,
60 type: type || 'all',
61 });
62 } catch (error) {
63 console.error('Miro items error:', error);
64 return NextResponse.json(
65 { error: 'Failed to fetch board items' },
66 { status: 500 }
67 );
68 }
69}

Pro tip: Miro boards can have thousands of items. Use the limit parameter (max 50 per request) and implement cursor-based pagination for boards with large amounts of content. The API returns a cursor field you pass as a query parameter in the next request.

Expected result: The board items route returns a structured list of items filtered by type. Sticky notes show their text content stripped of HTML. Shapes and frames return their geometry data.

4

Add Environment Variables in Vercel and Deploy

Add your Miro access token to Vercel environment variables so it is available to your API routes in production. In Vercel Dashboard, navigate to your project, click Settings → Environment Variables. Add MIRO_ACCESS_TOKEN with your Miro OAuth access token value. Set the scope to Production and Preview (you can use the same token for both in development). Never add NEXT_PUBLIC_ prefix to this variable — it must remain server-only. If you want a specific default board to embed, also add MIRO_DEFAULT_BOARD_ID with the board ID. Board IDs are in the Miro URL when you have a board open: https://miro.com/app/board/uXjVM_BOARD_ID=/ — copy everything between /board/ and =/. After adding environment variables, redeploy your Vercel project so the new variables are picked up. Go to Deployments in Vercel → click the three dots on your latest deployment → Redeploy. Alternatively, push a small commit to trigger a new deployment. Environment variable changes on Vercel require a redeployment to take effect — they are not hot-reloaded. To embed Miro boards without the API, use Miro's embed URL format in an iframe: https://miro.com/app/live-embed/BOARD_ID/ — this requires the board to have link sharing enabled. In Miro, open the board → Share → Copy link. For private boards on paid plans, you can use the embed URL with the access token as a query parameter for authenticated embedding.

app/components/MiroBoardEmbed.tsx
1// app/components/MiroBoardEmbed.tsx
2'use client';
3
4interface MiroBoardEmbedProps {
5 boardId: string;
6 height?: number;
7}
8
9export function MiroBoardEmbed({ boardId, height = 600 }: MiroBoardEmbedProps) {
10 const embedUrl = `https://miro.com/app/live-embed/${boardId}/`;
11
12 return (
13 <div className="w-full rounded-lg overflow-hidden border border-gray-200">
14 <iframe
15 src={embedUrl}
16 width="100%"
17 height={height}
18 frameBorder="0"
19 scrolling="no"
20 allow="fullscreen; clipboard-read; clipboard-write"
21 allowFullScreen
22 title="Miro Board"
23 />
24 </div>
25 );
26}

Pro tip: For the Miro embed to work, the board must have 'Anyone with the link can view' enabled in the Miro sharing settings. Boards set to 'Private' or 'Team only' will show an access denied screen in the iframe.

Expected result: MIRO_ACCESS_TOKEN is set in Vercel environment variables. The boards and items API routes work in production. The embed component displays the Miro board as an interactive canvas inside your app.

Common use cases

Team Dashboard with Live Miro Board Data

Build an internal team dashboard that pulls sprint boards, retrospective notes, or project planning canvases from Miro and displays key metrics alongside other project data. The dashboard shows a list of recent boards, item counts by type, and a preview of the latest updates without requiring team members to open Miro directly.

V0 Prompt

Create a team dashboard page that displays a list of Miro boards from /api/miro/boards. Show each board as a card with the board name, thumbnail, last modified date, and a count of items. Add a 'View Board' button that opens the Miro embed viewer. Include a loading skeleton while the boards are fetching.

Copy this prompt to try it in V0

Embedded Miro Board Viewer

Embed a specific Miro board as an interactive canvas inside your app, allowing users to view and interact with the board without leaving your application. This is ideal for project portals, client collaboration tools, or documentation sites where the Miro board is the primary artifact.

V0 Prompt

Build a page at /boards/[boardId] that embeds a Miro board in an iframe taking up the full viewport below a header. The header shows the board name fetched from /api/miro/boards/[boardId] with a title, description, and a 'Open in Miro' link. Make the iframe responsive and handle loading state.

Copy this prompt to try it in V0

Sprint Retrospective Note Extractor

Fetch sticky notes and text items from a Miro board used for sprint retrospectives and display them organized by frame or column. This allows teams to export insights from Miro boards into a structured view or feed the content into other systems without manual copy-paste.

V0 Prompt

Create a retrospective insights page that calls /api/miro/boards/[boardId]/items?type=sticky_note and displays sticky notes grouped by their frame (What went well, What to improve, Action items). Show each note as a colored card matching the sticky note color. Include a 'Copy all notes' button that copies the text content to clipboard.

Copy this prompt to try it in V0

Troubleshooting

API returns 401 Unauthorized — 'Not authorized to access the resource'

Cause: The MIRO_ACCESS_TOKEN environment variable is missing, expired, or does not have the required scopes (boards:read or board:item:read). Miro OAuth tokens expire after a set period unless you use a refresh token flow.

Solution: Go to developers.miro.com, open your app, and regenerate an access token. Make sure the token has boards:read and board:item:read scopes. Update MIRO_ACCESS_TOKEN in Vercel Dashboard → Settings → Environment Variables, then redeploy. For long-term integrations, implement the OAuth refresh token flow to automatically renew expired tokens.

API returns 404 — 'Board not found' for a valid board ID

Cause: The access token does not have permission to access that specific board, or the board ID is incorrectly formatted. Miro board IDs sometimes contain special characters like = that must be URL-encoded.

Solution: Verify the board ID from the Miro URL. Make sure the token belongs to a user or workspace that has access to the board. When constructing API URLs, use encodeURIComponent(boardId) to handle special characters. In your Next.js route, use params.boardId directly — Next.js automatically decodes path parameters.

typescript
1// Encode board ID when constructing the API URL
2const boardId = encodeURIComponent(params.boardId);
3const url = `https://api.miro.com/v2/boards/${boardId}/items`;

Miro board embed shows 'Access denied' or a login screen inside the iframe

Cause: The Miro board is set to 'Private' or 'Team only' access. Public embedding requires the board to have 'Anyone with the link can view' enabled in Miro's sharing settings.

Solution: Open the Miro board, click Share (top right), and under 'Invite teammates and more' change access to 'Anyone with the link can view'. For boards that must remain private, use Miro's authenticated embed with an access token query parameter, or use the API to extract content rather than embedding the board directly.

Sticky note content shows HTML tags like <p> and <strong> instead of plain text

Cause: Miro stores sticky note text as HTML with formatting tags. The raw API response includes these HTML tags in the data.content field.

Solution: Strip HTML tags server-side in your API route before returning the data to the frontend. Use a regex replace or the DOMParser API in a server environment to extract plain text from the HTML content.

typescript
1// Strip HTML from Miro sticky note content
2const plainText = item.data.content.replace(/<[^>]*>/g, '').trim();

Best practices

  • Cache Miro API responses in your Next.js route handlers using next: { revalidate: 60 } — board data changes infrequently and caching reduces API call volume against Miro's rate limits.
  • Store MIRO_ACCESS_TOKEN as a server-only environment variable without the NEXT_PUBLIC_ prefix — Miro tokens grant access to all boards the user can see and must never be exposed in the browser.
  • URL-encode board IDs when constructing Miro API URLs — board IDs contain characters like = that can break URL parsing if not properly encoded.
  • Use Miro's live embed URL for read-only board viewing rather than building a custom renderer — the embed is interactive, always up to date, and requires no additional API calls.
  • Implement cursor-based pagination for boards with many items — Miro's items endpoint returns maximum 50 items per request, and complex boards can have hundreds or thousands of items.
  • For multi-user applications, implement the full OAuth 2.0 flow so each user authenticates with their own Miro account — this respects per-user board access permissions rather than sharing one admin token.
  • Test your integration after Miro token expiration — access tokens expire and will cause silent 401 failures in production without a refresh token mechanism.

Alternatives

Frequently asked questions

Can V0 generate the Miro OAuth 2.0 flow automatically?

V0 can generate the code structure for an OAuth callback route, but it cannot test the flow or register callback URLs with Miro. You need to manually set the redirect URI in your Miro developer app at developers.miro.com to match your Vercel deployment URL. For simple single-team integrations, a static access token from the Miro developer portal is easier than implementing the full OAuth flow.

Does the Miro REST API work with the free Miro plan?

Yes, the Miro REST API is available on the free plan for development and testing. Free plan boards can be accessed via the API, and the developer portal allows generating access tokens at no cost. However, some advanced Miro features like private workspace access and higher API rate limits require paid plans.

How do I embed a private Miro board without making it public?

Private board embedding is only available on Miro's paid plans. For Starter and above plans, you can use Miro's authenticated embed URL that accepts an access token as a query parameter. For free plans, boards must have public link sharing enabled to embed. Alternatively, use the REST API to read the board content and render it in your own UI rather than embedding the Miro canvas.

What is the difference between the Miro REST API and Miro Web SDK?

The Miro REST API is for server-side integrations — fetching board data, creating items programmatically, and building dashboards. The Miro Web SDK (app SDK) is for building interactive apps that run inside the Miro canvas itself. For V0-generated Next.js apps, you use the REST API. The Web SDK is for Miro plugin development where your UI appears as a panel within Miro.

Can I write data back to a Miro board from my Next.js app?

Yes, the Miro REST API supports writing operations. With the boards:write and board:item:write scopes, you can create sticky notes, shapes, frames, and other items on a board via POST requests to the items endpoint. This enables use cases like automatically populating a Miro retrospective board from a form submission or creating cards on a planning board from your app.

How do I handle Miro API rate limits?

Miro allows 1000 API requests per minute per access token. For most dashboard apps, caching responses in Next.js route handlers using next: { revalidate: 60 } is sufficient to stay well within limits. If you exceed the limit, Miro returns a 429 Too Many Requests response. Implement exponential backoff retry logic for production apps and consider increasing the cache revalidation time for data that changes infrequently.

For complex Miro integrations, can someone help set up the OAuth flow?

For complex multi-user Miro integrations requiring a full OAuth 2.0 implementation with refresh tokens, the RapidDev team can help design and configure the authentication architecture for your V0-generated Next.js app.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.