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
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
Create a Miro Developer App and Get an Access Token
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.
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).
Create the Miro Boards API Route
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.
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
1import { NextResponse } from 'next/server';23const MIRO_API_BASE = 'https://api.miro.com/v2';45export 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 }1314 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 seconds21 });2223 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 }3031 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.
Create the Board Items API Route
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.
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
1import { NextRequest, NextResponse } from 'next/server';23const MIRO_API_BASE = 'https://api.miro.com/v2';45interface RouteParams {6 params: { boardId: string };7}89export 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 }1718 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);2223 const typeParam = type ? `&type=${type}` : '';24 const url = `${MIRO_API_BASE}/boards/${boardId}/items?limit=${limit}${typeParam}`;2526 try {27 const response = await fetch(url, {28 headers: {29 Authorization: `Bearer ${token}`,30 Accept: 'application/json',31 },32 });3334 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 }4142 const data = await response.json();4344 // Clean HTML from sticky note content45 const items = data.data.map((item: { type: string; data?: { content?: string }; [key: string]: unknown }) => ({46 ...item,47 data: item.data48 ? {49 ...item.data,50 content: item.data.content51 ? item.data.content.replace(/<[^>]*>/g, '')52 : '',53 }54 : item.data,55 }));5657 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.
Add Environment Variables in Vercel and Deploy
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.
1// app/components/MiroBoardEmbed.tsx2'use client';34interface MiroBoardEmbedProps {5 boardId: string;6 height?: number;7}89export function MiroBoardEmbed({ boardId, height = 600 }: MiroBoardEmbedProps) {10 const embedUrl = `https://miro.com/app/live-embed/${boardId}/`;1112 return (13 <div className="w-full rounded-lg overflow-hidden border border-gray-200">14 <iframe15 src={embedUrl}16 width="100%"17 height={height}18 frameBorder="0"19 scrolling="no"20 allow="fullscreen; clipboard-read; clipboard-write"21 allowFullScreen22 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.
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.
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.
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.
1// Encode board ID when constructing the API URL2const 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.
1// Strip HTML from Miro sticky note content2const 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
Mural is a structured visual facilitation tool focused on guided templates and workshops, making it better for teams that need facilitated collaboration rather than freeform visual brainstorming.
Lucidchart specializes in structured diagrams and flowcharts with a richer API for reading diagram data, making it better if your use case is technical diagramming rather than open-ended visual collaboration.
Notion is better suited when your team needs structured pages and databases rather than visual canvases, and its API provides richer structured data extraction.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation