# How to Integrate Bolt.new with Vimeo

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 20 minutes
- Last updated: April 2026

## TL;DR

Connect Bolt.new to Vimeo's REST API to fetch video metadata as JSON, embed videos, and build video gallery pages. For quick embeds without authentication, use Vimeo's OEmbed endpoint — no API key needed. For private videos, upload management, and analytics, get a personal access token from the Vimeo Developer Dashboard and use a Next.js API route to proxy calls. Vimeo's API returns standard JSON and works fully in Bolt's WebContainer.

## Build Vimeo-Powered Video Experiences in Bolt.new

Vimeo's API is one of the most developer-friendly video APIs available — it returns clean JSON, has comprehensive documentation, and provides both unauthenticated OEmbed endpoints and a full REST API with OAuth. The fact that the search queries specifically include 'vimeo json integration' and 'connect vimeo to json' shows that developers primarily want to fetch Vimeo data programmatically and use it in their apps, not just embed iframes manually.

The OEmbed endpoint is the fastest path to video embeds: send a GET request to `https://vimeo.com/api/oembed.json?url=https://vimeo.com/VIDEO_ID` and get back a complete embed HTML snippet, thumbnail URL, video title, description, and dimensions — no API key required. This endpoint works from any origin and is perfect for public videos you want to embed without setting up OAuth.

For private videos, batch fetching from a user's portfolio, or accessing video analytics (plays, likes, comments), you need a personal access token from the Vimeo Developer Dashboard. The full REST API (`api.vimeo.com`) requires a Bearer token and has CORS restrictions for client-side calls — use a Next.js API route to proxy these calls server-side. The Vimeo API is entirely HTTP-based with no native modules required, so it works perfectly in Bolt.new's WebContainer for development.

## Before you start

- A Vimeo account — free or paid (some API features require Vimeo Pro or higher)
- A Vimeo personal access token from developer.vimeo.com — create an app, then generate a token with the scopes public, private, and video_files
- A Bolt.new account with a new Next.js project open
- Your Vimeo user ID (find it in your Vimeo account URL or via the API at api.vimeo.com/me)

## Step-by-step guide

### 1. Set up Vimeo API credentials and understand the two access patterns

Vimeo's API has two usage patterns with different authentication requirements. Understanding the difference helps you choose the right approach for your use case.

The OEmbed pattern requires no credentials at all. Send a GET request to `https://vimeo.com/api/oembed.json?url=https://vimeo.com/YOUR_VIDEO_ID` and receive JSON with the complete embed code, thumbnail URL, video title, author name, and dimensions. This is public and works from any client or server — perfect for embedding known public videos without setting up API keys.

The REST API pattern requires a personal access token for private videos, user-specific data, and higher rate limits. Go to developer.vimeo.com, log in, and create an app. Under 'Personal Access Tokens', generate a token with the scopes you need: `public` (public videos), `private` (private videos), `video_files` (direct video file URLs for download), and `interact` (likes, comments). The token is a long string starting with a series of numbers — copy it immediately as you cannot view it again after the page closes.

Your Vimeo user ID is the number in your profile URL (`vimeo.com/USER_ID`). You can also find it by calling `api.vimeo.com/me` with your token. Add both to your .env.local file. Do not commit these to version control.

Note on rate limits: unauthenticated OEmbed calls are limited per IP. Authenticated API calls have higher limits (1,000 requests per minute on Pro plans). For high-traffic apps, cache API responses rather than fetching fresh data on every page load.

```
// lib/vimeo.ts
const VIMEO_API_BASE = 'https://api.vimeo.com';

export interface VimeoVideo {
  uri: string;
  name: string;
  description: string | null;
  duration: number;
  width: number;
  height: number;
  created_time: string;
  stats: { plays: number };
  pictures: {
    sizes: Array<{ width: number; height: number; link: string; link_with_play_button: string }>;
  };
  embed: { html: string };
  link: string;
}

export interface VimeoCollection {
  total: number;
  page: number;
  per_page: number;
  paging: { next: string | null; previous: string | null };
  data: VimeoVideo[];
}

export async function vimeoFetch<T>(path: string): Promise<T> {
  const token = process.env.VIMEO_ACCESS_TOKEN;
  if (!token) throw new Error('VIMEO_ACCESS_TOKEN is not set');

  const url = path.startsWith('http') ? path : `${VIMEO_API_BASE}${path}`;
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/vnd.vimeo.*+json;version=3.4',
      'Content-Type': 'application/json',
    },
    next: { revalidate: 300 }, // Cache for 5 minutes in Next.js
  });

  if (response.status === 401) {
    throw new Error('Invalid Vimeo access token');
  }
  if (response.status === 404) {
    throw new Error('Vimeo resource not found');
  }
  if (!response.ok) {
    const error = await response.json().catch(() => ({ error: 'Unknown error' }));
    throw new Error(error.error ?? `Vimeo API error: ${response.status}`);
  }

  return response.json();
}

export function getThumbnail(video: VimeoVideo, preferredWidth = 640): string {
  const sizes = video.pictures.sizes;
  const closest = sizes.reduce((prev, curr) =>
    Math.abs(curr.width - preferredWidth) < Math.abs(prev.width - preferredWidth) ? curr : prev
  );
  return closest.link_with_play_button ?? closest.link;
}

export function formatDuration(seconds: number): string {
  const m = Math.floor(seconds / 60);
  const s = seconds % 60;
  return `${m}:${s.toString().padStart(2, '0')}`;
}
```

**Expected result:** A configured Vimeo helper library in lib/vimeo.ts with typed interfaces, error handling, and a 5-minute cache using Next.js fetch caching.

### 2. Build the OEmbed endpoint for instant video embeds

The OEmbed approach is the fastest way to embed Vimeo videos in your Bolt app. Create a Next.js API route that proxies OEmbed requests to avoid browser CORS restrictions on direct client-side calls. The Vimeo OEmbed endpoint supports useful parameters: `width` and `height` to constrain embed dimensions, `autoplay=1` to start playing on load, `loop=1` for looping, and `title=0` to hide the video title overlay.

The OEmbed response contains a complete iframe embed code in the `html` field. You can render this directly using `dangerouslySetInnerHTML` in React — this is safe because the HTML comes from Vimeo's trusted OEmbed endpoint, not user input. The response also includes the thumbnail URL, video title, author name, and the original video dimensions which you need to calculate the aspect ratio.

For responsive embeds, do not use the hardcoded width and height from the iframe HTML. Instead, wrap the embed in a container div with `padding-bottom: 56.25%` (for 16:9) and `position: relative`, then set the iframe to `position: absolute; inset: 0; width: 100%; height: 100%`. This technique (the intrinsic ratio trick) makes the embed scale with the container without JavaScript.

For performance, avoid calling OEmbed on every page render. If you know the Vimeo video IDs in advance, pre-fetch the OEmbed data at build time using Next.js static generation and cache the results.

```
// app/api/vimeo/oembed/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const videoUrl = request.nextUrl.searchParams.get('url');

  if (!videoUrl || !videoUrl.includes('vimeo.com')) {
    return NextResponse.json({ error: 'Valid Vimeo URL required' }, { status: 400 });
  }

  const oembedUrl = new URL('https://vimeo.com/api/oembed.json');
  oembedUrl.searchParams.set('url', videoUrl);
  oembedUrl.searchParams.set('width', '1280');
  oembedUrl.searchParams.set('responsive', 'true');

  const response = await fetch(oembedUrl.toString(), {
    next: { revalidate: 3600 }, // Cache OEmbed responses for 1 hour
  });

  if (!response.ok) {
    return NextResponse.json(
      { error: 'Video not found or not embeddable' },
      { status: response.status }
    );
  }

  const data = await response.json();
  return NextResponse.json(data);
}

// app/components/VimeoEmbed.tsx
'use client';
import { useState, useEffect } from 'react';

interface OEmbedData {
  html: string;
  title: string;
  thumbnail_url: string;
  width: number;
  height: number;
  author_name: string;
}

export function VimeoEmbed({ vimeoUrl, className = '' }: { vimeoUrl: string; className?: string }) {
  const [oembed, setOembed] = useState<OEmbedData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    fetch(`/api/vimeo/oembed?url=${encodeURIComponent(vimeoUrl)}`)
      .then(r => r.json())
      .then(data => {
        if (data.error) throw new Error(data.error);
        setOembed(data);
      })
      .catch(() => setError(true))
      .finally(() => setLoading(false));
  }, [vimeoUrl]);

  if (loading) return (
    <div className={`bg-gray-900 animate-pulse rounded-lg aspect-video ${className}`} />
  );

  if (error || !oembed) return (
    <div className={`bg-gray-900 rounded-lg aspect-video flex items-center justify-center ${className}`}>
      <p className="text-gray-400 text-sm">Video unavailable</p>
    </div>
  );

  return (
    <div
      className={`relative aspect-video rounded-lg overflow-hidden ${className}`}
      dangerouslySetInnerHTML={{ __html: oembed.html }}
    />
  );
}
```

**Expected result:** A VimeoEmbed component that renders responsive Vimeo iframes from a URL prop, with loading skeleton and error fallback states.

### 3. Build the video library API routes with pagination

Fetching a user's video library requires the authenticated REST API. The Vimeo API uses cursor-based pagination via the `paging.next` field in responses — when you have more videos than the per_page limit, `paging.next` contains a URL for the next page. Implement this pagination so your gallery can display large video collections.

The key endpoint for fetching user videos is `GET /users/{user_id}/videos`. Add query parameters to control the response: `fields` to request only the data you need (reduces response size significantly), `per_page` for the page size (max 100), `page` for pagination, and `sort` with `date` or `plays` to control ordering.

Using the `fields` parameter is crucial for performance. Without it, Vimeo returns all metadata for every video including file download URLs, review links, tags, and embed codes for every size. A response for 20 videos without field filtering can be 50-100KB. With `fields=uri,name,description,duration,pictures.sizes,stats.plays,embed.html,link`, the same response is under 10KB.

For the gallery UI, display a 2-3 column responsive grid with thumbnail cards. Each card shows the thumbnail with a play button overlay, video title, duration, and view count. Clicking a card opens the full embed in a modal or navigates to a detail page. Add 'Load More' pagination rather than infinite scroll — it is simpler to implement and better for users on slower connections.

```
// app/api/vimeo/videos/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { vimeoFetch, VimeoCollection } from '@/lib/vimeo';

const FIELDS = [
  'uri', 'name', 'description', 'duration', 'created_time',
  'stats.plays', 'pictures.sizes', 'embed.html', 'link', 'width', 'height',
].join(',');

export async function GET(request: NextRequest) {
  const page = request.nextUrl.searchParams.get('page') ?? '1';
  const perPage = Math.min(
    parseInt(request.nextUrl.searchParams.get('per_page') ?? '12'),
    100
  );
  const userId = process.env.VIMEO_USER_ID;

  if (!userId) {
    return NextResponse.json({ error: 'VIMEO_USER_ID not configured' }, { status: 500 });
  }

  try {
    const data = await vimeoFetch<VimeoCollection>(
      `/users/${userId}/videos?fields=${FIELDS}&per_page=${perPage}&page=${page}&sort=date`
    );
    return NextResponse.json(data);
  } catch (error) {
    const message = error instanceof Error ? error.message : 'Unknown error';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** Paginated video library API that returns typed video data with thumbnails and embed code, powering a gallery page with Load More pagination.

### 4. Deploy to Netlify and configure environment variables

The Vimeo integration is fully functional in Bolt.new's WebContainer for development — all outbound API calls to Vimeo's REST endpoint and OEmbed work in the preview. When you are ready for production, deploy to Netlify or Bolt Cloud.

In Bolt.new, click the Deploy button and connect to Netlify via OAuth. Bolt will build your Next.js project and deploy it automatically. After the initial deployment, go to Netlify's dashboard → Site Configuration → Environment Variables and add `VIMEO_ACCESS_TOKEN` and `VIMEO_USER_ID`. Trigger a redeploy (Deploys → Trigger deploy) to make the variables available.

One Bolt.new WebContainer limitation worth noting: for video uploads to Vimeo, the standard Vimeo upload flow requires reading large binary files from disk and streaming them to Vimeo's upload API. The WebContainer's ephemeral in-memory file system is not suitable for large file handling — files exist only in memory during the session and there is no persistent local storage. For video upload features, implement them after deployment where a real server environment can handle large file streams. Use multipart form data and stream the file directly to Vimeo's upload endpoint without reading the entire file into memory.

For private videos, ensure your Vimeo access token has the `private` scope. Videos set to 'Only me' or 'Password protected' in Vimeo require this scope; otherwise the API returns 404 for those video IDs even when the correct token is provided.

```
// netlify.toml
[build]
  command = "npm run build"
  publish = ".next"

[[plugins]]
  package = "@netlify/plugin-nextjs"

[build.environment]
  NODE_VERSION = "20"

# Cache Vimeo thumbnails aggressively
[[headers]]
  for = "/api/vimeo/*"
  [headers.values]
    Cache-Control = "s-maxage=300, stale-while-revalidate=60"
```

**Expected result:** A deployed Vimeo-powered video gallery on Netlify with paginated grid, individual video detail pages, and OEmbed player rendering.

## Best practices

- Use the fields parameter in every Vimeo API request to limit response size — requesting only the fields you need reduces payload by up to 90% for video-heavy pages
- Cache Vimeo API responses using Next.js fetch caching (next: { revalidate: 300 }) — Vimeo video metadata changes infrequently and does not need fresh fetches on every request
- Store VIMEO_ACCESS_TOKEN only in server-side environment variables and never in NEXT_PUBLIC_ prefixed vars — the token provides access to your private Vimeo account
- Use the OEmbed endpoint for simple public video embeds rather than the authenticated API — it requires no credentials, has generous rate limits, and provides ready-to-use responsive embed HTML
- Handle the case where pictures.sizes is empty or the embed HTML is unavailable — some videos may have privacy settings that prevent embedding outside of Vimeo
- Implement 'Load More' pagination rather than loading all videos at once — a portfolio with 100+ videos should never load all metadata on the initial page render
- For video upload features, always implement them on a deployed server (Netlify or Bolt Cloud) rather than in Bolt's WebContainer preview — large file handling requires a persistent file system
- Add the Vimeo video ID to image alt text and page titles for SEO — search engines can index Vimeo-powered pages if you provide proper text metadata alongside the embed

## Use cases

### Video Portfolio Gallery

Fetch all videos from a Vimeo user account and display them in a responsive masonry or grid gallery with thumbnails, titles, view counts, and click-to-play modals. Perfect for creative professionals or agencies showcasing their work.

Prompt example:

```
Build a Vimeo video portfolio gallery in Next.js. Create an API route at /api/vimeo/videos that fetches all videos from a Vimeo user ID (from env var VIMEO_USER_ID) using a personal access token (VIMEO_ACCESS_TOKEN). Return each video's id, name, description, duration, pictures.sizes (thumbnails), stats.plays, and embed.html. Display a responsive 3-column grid of video thumbnail cards. When clicked, show a modal with the Vimeo embed player. Use Tailwind CSS with a dark cinema-style theme.
```

### Client Video Delivery Portal

A private portal where clients log in to view their commissioned videos stored in a specific Vimeo folder or album. Fetch videos by album ID, display them in a clean player interface, and show metadata like delivery date and video specs.

Prompt example:

```
Create a client video delivery portal in Next.js. Add an API route at /api/vimeo/album/[albumId] that fetches all videos in a specific Vimeo album using a personal access token. Build a portal page that shows video thumbnails in a list with title, duration, and creation date. When a client clicks a video, expand an embedded Vimeo player inline using the video's embed.html. Add a search input to filter videos by title client-side. Use process.env.VIMEO_ACCESS_TOKEN for the Bearer token.
```

### Blog with Embedded Video Posts

Add rich video embeds to blog posts by fetching Vimeo OEmbed data for video URLs stored in content. The OEmbed endpoint requires no API key, making it ideal for content-driven sites where authors just paste Vimeo URLs into their posts.

Prompt example:

```
Add Vimeo OEmbed support to my blog. Create an API route at /api/vimeo/oembed that accepts a Vimeo video URL as a query param, fetches the OEmbed data from https://vimeo.com/api/oembed.json, and returns the embed HTML, thumbnail URL, title, author name, and video dimensions. Create a VimeoEmbed React component that accepts a Vimeo URL prop, calls this API route, and renders a responsive embedded player (aspect ratio 16:9). Handle loading and error states gracefully.
```

## Troubleshooting

### Vimeo API returns 401 Unauthorized even with a valid-looking access token

Cause: The personal access token either expired, was deleted from the Vimeo Developer Dashboard, or does not have the required scope for the endpoint being accessed. Vimeo tokens do not automatically expire but can be revoked.

Solution: Go to developer.vimeo.com → your app → Personal Access Tokens and check if the token still exists. If it was deleted, generate a new one with the required scopes (public, private, video_files). Update VIMEO_ACCESS_TOKEN in your .env.local and in Netlify's environment variables, then redeploy.

### Vimeo embed HTML renders as plain text (the iframe code shows as literal text, not a player)

Cause: The embed.html field contains raw HTML with the iframe tag, but React's default JSX rendering escapes HTML for security. Rendering it as {oembed.html} shows the literal string rather than interpreting it as HTML.

Solution: Use dangerouslySetInnerHTML to render Vimeo's embed HTML. This is safe for content from Vimeo's trusted OEmbed endpoint but should never be used with user-generated content.

```
// WRONG - renders as escaped text:
<div>{video.embed.html}</div>

// CORRECT - renders as actual iframe:
<div dangerouslySetInnerHTML={{ __html: video.embed.html }} />
```

### Private Vimeo videos return 404 even though they exist in the Vimeo account

Cause: The access token was generated without the 'private' scope. Videos set to 'Only me' or with restricted viewing permissions return 404 rather than 403 for unauthorized requests — this is intentional to avoid information leakage.

Solution: Generate a new personal access token with the 'private' scope checked. You cannot add scopes to an existing token — delete the old one and create a new one. Update VIMEO_ACCESS_TOKEN and redeploy.

### Gallery grid loads slowly on the initial page render with many videos

Cause: Each video card is making a separate fetch request for OEmbed data, causing N+1 requests on page load. With 20 videos, this is 20 simultaneous API calls causing visible layout shift and slow rendering.

Solution: Pre-fetch the thumbnail data in the API route using the pictures.sizes field from the main videos endpoint, rather than making separate OEmbed calls per card. Reserve OEmbed calls only for when a user clicks to play a video, not for the thumbnail grid.

```
// In the gallery, use pictures.sizes from the videos endpoint for thumbnails
// Only fetch OEmbed when the user clicks to play:
function VideoCard({ video }: { video: VimeoVideo }) {
  const [showEmbed, setShowEmbed] = useState(false);
  const thumbnail = getThumbnail(video, 640); // From lib/vimeo.ts

  return (
    <div onClick={() => setShowEmbed(true)}>
      {showEmbed
        ? <VimeoEmbed vimeoUrl={video.link} /> // Fetches oembed only on click
        : <img src={thumbnail} alt={video.name} /> // Uses pre-fetched thumbnail
      }
    </div>
  );
}
```

## Frequently asked questions

### Can I connect Vimeo to JSON in Bolt.new without authentication?

Yes, using Vimeo's OEmbed endpoint at vimeo.com/api/oembed.json. Pass any public Vimeo URL as the 'url' parameter and receive JSON with the embed code, thumbnail URL, video title, and dimensions — no API key required. For private videos, user libraries, and analytics data, you need a personal access token from the Vimeo Developer Dashboard.

### How do I get Vimeo video data as JSON in my Bolt.new app?

Create a Next.js API route that calls the Vimeo REST API at api.vimeo.com with a Bearer token from your environment variables. The API returns JSON for all endpoints — video details, user libraries, albums, and analytics. Use the fields parameter to request only the JSON keys you need, which dramatically reduces response size for video-heavy pages.

### Does Vimeo video embedding work in Bolt.new's WebContainer preview?

Yes. Vimeo iframes and the OEmbed endpoint work in Bolt's WebContainer preview — these are purely client-side browser features with no WebContainer restrictions. You can develop and test Vimeo embeds entirely in the Bolt preview. API route calls to the Vimeo REST API also work in the preview since they are outbound HTTP requests.

### Can I upload videos to Vimeo from my Bolt.new app?

Vimeo supports file uploads via its API, but this is best implemented on a deployed server rather than in Bolt's WebContainer. The WebContainer has an ephemeral in-memory file system with no persistent storage — large file uploads can exceed browser memory limits. Deploy to Netlify or Bolt Cloud first, then implement the upload flow using Vimeo's tus-based upload protocol.

### Do I need a paid Vimeo plan to use the API?

A free Vimeo account provides basic API access for fetching public video data and OEmbed. However, free accounts have storage limits (500MB/week), and some API features like private video access and advanced analytics require a Vimeo Pro plan ($20/month). The personal access token is available on all plan levels.

### How do I make Vimeo embeds responsive in my Bolt.new app?

Pass responsive=true in the OEmbed request to get Vimeo's built-in responsive embed HTML. Alternatively, wrap the embed div in a container with a 16:9 aspect ratio using Tailwind's aspect-video class or CSS padding-bottom: 56.25%. Set the iframe inside to width: 100% and height: 100% with absolute positioning. The VimeoEmbed component in this tutorial implements both approaches.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/vimeo
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/vimeo
