A few months ago I started exploring how video-to-audio conversion tools work under the hood. The result was SSV TikTok — a free TikTok to MP3 converter that processes files in under 5 seconds.
Along the way I learned a lot about media processing, edge functions, and the technical challenges of working with platforms that change their systems frequently.
In this post I'll walk through the tech stack, architecture decisions, performance optimizations, and the surprising lessons that came from building a high-traffic media conversion tool. If you're thinking about building something similar, hopefully this saves you some time.
The Problem
Users want to extract audio from TikTok videos as MP3 files. Simple in concept, harder in practice because:
TikTok doesn't expose a public API for this
Their video URLs and metadata structure change often
Audio extraction requires server-side processing (you can't do it cleanly in the browser alone)
Users expect sub-5-second response times
Mobile browsers handle downloads differently than desktop
Tech Stack Choices
After evaluating options, here's what I landed on:
Frontend: Next.js 14 with App Router
Next.js made sense because:
Server components reduce client bundle size
Built-in API routes mean no separate backend
Excellent caching primitives
Vercel deployment integrates seamlessly
// app/page.js
'use client';
import { useState } from 'react';
export default function Converter() {
const [url, setUrl] = useState('');
const [quality, setQuality] = useState('320');
const [loading, setLoading] = useState(false);
const handleConvert = async () => {
setLoading(true);
try {
const response = await fetch('/api/convert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, quality }),
});
const data = await response.json();
if (data.downloadUrl) {
window.location.href = data.downloadUrl;
}
} catch (error) {
console.error('Conversion failed:', error);
} finally {
setLoading(false);
}
};
return (
<main>
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="Paste TikTok URL here"
/>
<select value={quality} onChange={(e) => setQuality(e.target.value)}>
<option value="320">320 kbps (Best)</option>
<option value="192">192 kbps (Balanced)</option>
<option value="128">128 kbps (Smallest)</option>
</select>
<button onClick={handleConvert} disabled={loading || !url}>
{loading ? 'Converting...' : 'Convert to MP3'}
</button>
</main>
);
}
Backend: Serverless Functions
The conversion happens in Vercel Edge Functions for low latency globally. The flow is:
Validate the TikTok URL
Fetch video metadata
Stream the audio track through FFmpeg
Return a signed URL for download
Media Processing: FFmpeg
FFmpeg is the gold standard for media manipulation. The Node wrapper fluent-ffmpeg makes it manageable:
import ffmpeg from 'fluent-ffmpeg';
async function extractAudio(videoUrl, bitrate, outputPath) {
return new Promise((resolve, reject) => {
ffmpeg(videoUrl)
.audioBitrate(parseInt(bitrate))
.audioCodec('libmp3lame')
.format('mp3')
.on('end', () => resolve(outputPath))
.on('error', reject)
.save(outputPath);
});
}
Storage: Temporary Cloud Storage
Files live in temporary storage for 5 minutes after conversion, then auto-delete. This minimizes storage costs and respects user privacy.
The Challenges I Didn't See Coming
Challenge 1: TikTok Changes Their System Frequently
This was the biggest lesson from building SSV TikTok. Every 2-4 weeks, TikTok adjusts something — URL structures, watermark embedding, CDN endpoints, response formats. Building a TikTok to MP3 converter that doesn't break requires constant adaptation.
The solution was abstracting the TikTok fetcher behind a clean interface:
class TikTokFetcher {
async fetchVideo(url) {
const videoId = this.extractVideoId(url);
const meta = await this.getMetadata(videoId);
return {
videoUrl: meta.playAddr,
audioUrl: meta.musicInfo?.playUrl,
duration: meta.duration,
author: meta.author,
};
}
extractVideoId(url) {
const patterns = [
/vm\.tiktok\.com\/([A-Za-z0-9]+)/,
/tiktok\.com\/@[\w.-]+\/video\/(\d+)/,
/tiktok\.com\/t\/([A-Za-z0-9]+)/,
];
for (const pattern of patterns) {
const match = url.match(pattern);
if (match) return match[1];
}
throw new Error('Invalid TikTok URL');
}
}
When TikTok changes things, I only need to update one module instead of refactoring the whole codebase.
Challenge 2: Mobile Browser Downloads
Mobile browsers — especially iOS Safari — handle downloads differently than desktop. My first deploy worked perfectly on Chrome desktop but failed on iOS, where Safari would preview the MP3 instead of downloading it.
The fix involves proper response headers:
export async function GET(request) {
const audioStream = await getAudioStream(/* ... */);
return new Response(audioStream, {
headers: {
'Content-Type': 'audio/mpeg',
'Content-Disposition': 'attachment; filename="audio.mp3"',
'Cache-Control': 'no-cache',
},
});
}
The Content-Disposition: attachment header forces Safari to trigger a save dialog instead of inline playback.
Challenge 3: Memory on Serverless Functions
Loading entire video files into memory crashed my first deployments on longer TikToks. The fix was streaming:
import { Readable } from 'stream';
async function streamConvert(videoUrl, outputPath, bitrate) {
return new Promise((resolve, reject) => {
ffmpeg(videoUrl)
.audioBitrate(bitrate)
.audioCodec('libmp3lame')
.format('mp3')
.pipe(fs.createWriteStream(outputPath))
.on('finish', resolve)
.on('error', reject);
});
}
Streaming reduced memory usage by ~80% and let the function handle videos of any length without OOM errors.
Challenge 4: Rate Limiting Without a Database
I needed rate limiting to prevent abuse but didn't want to add a database. Solution: in-memory LRU cache:
import { LRUCache } from 'lru-cache';
const rateLimit = new LRUCache({
max: 10000,
ttl: 60 * 1000, // 1 minute window
});
export function checkRateLimit(ip) {
const count = (rateLimit.get(ip) || 0) + 1;
rateLimit.set(ip, count);
return count <= 10; // 10 requests per minute per IP
}
For higher scale, this would move to Redis or Upstash. At current traffic levels, in-memory works fine.
Performance Optimizations That Mattered
The SSV TikTok converter consistently scores 95+ on Lighthouse. Here's what made the difference:
Edge Functions for the API
Moving conversion from regional functions to Vercel Edge dropped global latency from ~300ms to under 100ms.Streaming Responses
Instead of waiting for the entire MP3 to be ready before sending, the response streams as FFmpeg produces audio data. Perceived speed improved dramatically.Aggressive Caching for Static Pages
Static parts of the site are cached at the CDN:
export const revalidate = 86400; // Once per day
export default async function HomePage() {
return <ConverterUI />;
}
- Image Optimization
All static images use Next.js
<Image>with WebP/AVIF formats:
<Image
src="/hero.png"
alt="TikTok to MP3 converter interface"
width={1200}
height={600}
priority
sizes="100vw"
/>
LCP dropped from 2.3s to 0.6s after this change.
Lessons for Developers Building Media Tools
If you're building something similar, here's what I'd suggest:
Abstract platform-specific code early. TikTok changes things. YouTube changes things. Twitter changes things. Build a clean abstraction so updates affect one file, not your whole codebase.
Stream everything you can. Memory limits will bite you eventually. Streaming is harder upfront but pays off massively in scalability.
Test on real mobile devices. Browser DevTools mobile emulation lies. iOS Safari has its own quirks. Test on actual phones.
Plan for rate limiting from day one. A free tool will attract abuse. Plan for it.
Privacy matters. Don't store user files unnecessarily. Auto-delete with TTL. Users notice and appreciate this.
Focus on the boring stuff. Page speed, error handling, mobile compatibility, clean UI. These matter more than fancy features.
Conclusion
Building a TikTok to MP3 converter is a great project for learning practical web development — media processing, edge functions, mobile compatibility, performance optimization. The constraints force you to write tight, efficient code.
If you want to see the final result, SSV TikTok is live and free. Feel free to test the conversion speed and quality yourself — it's a good benchmark for whatever you might build.
What's the most interesting media processing tool you've built? I'd love to hear about your challenges in the comments.
Top comments (0)