Documentation Index
Fetch the complete documentation index at: https://mintlify.com/KevxxAlva/tiktok-bot-downloader/llms.txt
Use this file to discover all available pages before exploring further.
TikTokSaver’s frontend lives entirely in the client/ package. It is a React 19 single-page application bundled by Vite 7, styled with Tailwind CSS v4, animated with Framer Motion v12, and routed with React Router v7. The entire build produces a static dist/ directory that Vercel serves directly, with all /api/* requests proxied to the Express serverless function.
| Tool | Version | Role |
|---|
| React | ^19.2.4 | UI component model and state management |
| Vite | ^7.2.4 | Dev server, HMR, and production bundler |
| TypeScript | ~5.9.3 | Static typing across all source files |
| Tailwind CSS | ^4.1.18 | Utility-first styling via @tailwindcss/vite plugin |
| Framer Motion | ^12.33.0 | Entrance animations and AnimatePresence transitions |
| React Router | ^7.13.0 | Client-side routing (BrowserRouter) |
| Axios | ^1.13.4 | HTTP requests to the backend API |
| react-helmet-async | ^2.0.5 | Per-page <head> meta tag injection |
| react-ga4 | ^2.1.0 | Google Analytics 4 pageview tracking |
| lucide-react | ^0.563.0 | Icon set (Waves, Download, Music, Mail, etc.) |
Tailwind CSS v4 is loaded as a Vite plugin — no tailwind.config.js file is needed. The plugin is registered in vite.config.ts alongside the standard React plugin:
// client/vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
secure: false,
}
}
}
})
The server.proxy configuration forwards every /api/* request to the local Express server during development, so the frontend never needs to know which environment it is running in.
Routing
App.tsx defines three client-side routes using BrowserRouter:
// client/src/App.tsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import PrivacyPolicy from './pages/PrivacyPolicy';
import TermsOfService from './pages/TermsOfService';
import GoogleAnalytics from './components/GoogleAnalytics';
function App() {
return (
<Router>
<GoogleAnalytics />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/privacy" element={<PrivacyPolicy />} />
<Route path="/terms" element={<TermsOfService />} />
</Routes>
</Router>
);
}
GoogleAnalytics sits outside the <Routes> tree so it receives every route change and fires a GA4 pageview on each navigation.
Design system
All design tokens are declared as CSS custom properties in client/src/index.css and consumed throughout the stylesheet via var(). Tailwind utility classes reference these values when building component-level styles.
| Token | Value | Usage |
|---|
--bg-dark | #0e0e0e | Page background |
--surface-dark | #1a1a1a | Cards, drawers, toggle backgrounds |
--surface-highlight | #252525 | Input field background |
--accent-lime | #ccff00 | Primary CTA buttons, active states, accent icons |
--text-main | #ffffff | Primary body text |
--text-muted | #888888 | Placeholder text, secondary labels |
--border-color | #333333 | All borders and dividers |
The stylesheet also ships three reusable utility classes that are applied directly in JSX:
/* Minimal dark input */
.input-minimal { background: var(--surface-highlight); border: 1px solid var(--border-color); }
.input-minimal:focus { border-color: var(--accent-lime); background: #2a2a2a; }
/* Lime action button */
.btn-lime { background-color: var(--accent-lime); color: #000000; font-weight: 700; }
.btn-lime:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(204,255,0,0.3); }
/* Dark card surface */
.card-matte { background-color: var(--surface-dark); border: 1px solid var(--border-color); border-radius: 12px; }
The body receives a subtle radial gradient from #1a1a1a at the top to var(--bg-dark) further down, creating visual depth without a separate background component.
Component tree
Every page composes a small set of shared layout components. Below is a description of each:
Header — A top navigation bar rendered by Home.tsx. Displays the TikTokSaver wordmark next to a lime Waves icon, a hidden-on-mobile “Cómo funciona” anchor link that scrolls to #Guia, and a Mail icon linking to the contact address.
Footer — A bottom bar with copyright text, a contact mail icon, and React Router <Link> elements to /privacy and /terms. Includes a disclaimer stating the tool is not affiliated with TikTok or ByteDance.
SeoHead — Wraps react-helmet-async’s <Helmet> to inject <title>, <meta name="description">, Open Graph tags, Twitter Card tags, and a canonical <link> for each page. Accepts title, description, keywords, image, and url props.
VideoResult — The result card that appears after a successful API call. Detects whether the response contains images (slideshow) or a standard video. For slideshows it renders a 2-column grid of proxied thumbnail images, each with an individual download button. For videos it shows the proxied cover image with an overlay caption. All download buttons call window.location.href = /api/proxy-download?url=...&filename=... to trigger a native browser download.
GoogleAnalytics — A render-less component that initializes the react-ga4 library with the VITE_GA_MEASUREMENT_ID environment variable on first mount and sends a pageview hit on every location change.
State in Home.tsx
The main page manages five pieces of state:
| State variable | Type | Purpose |
|---|
url | string | The raw TikTok URL typed or pasted by the user |
loading | boolean | Toggles the spinner and disables the submit button |
data | DownloadResult | null | Populated on a successful API response |
error | string | Displayed in the error banner on API failure |
mode | 'video' | 'audio' | Drives the Video/Audio toggle UI (visual only) |
The handleDownload function encodes the URL, calls axios.get('/api/download?url=...'), and either sets data or error depending on whether the response shape contains a result property.
TypeScript interfaces
The contract between the backend’s JSON response and the frontend’s rendering logic is expressed in client/src/types.ts.
DownloadOption
DownloadResult
Represents a single downloadable asset returned by the API — a clean video, watermarked video, or audio track.export interface DownloadOption {
type: 'hd' | 'normal' | 'music' | 'watermark';
label: string;
url: string;
size?: number | string;
}
| Field | Description |
|---|
type | Asset category — controls button color and icon in VideoResult |
label | Human-readable button label ("Sin Marca", "Con Marca (HD)", "Audio MP3") |
url | Absolute CDN URL, passed directly to /api/proxy-download |
size | File size in bytes (optional); rendered as MB in the button label |
The top-level shape returned by GET /api/download and stored in the data state variable.export interface DownloadResult {
result: {
downloads: DownloadOption[];
video: string[];
images?: string[];
music: string;
cover: string;
desc: string;
author: {
nickname: string;
avatar: string;
};
};
}
| Field | Description |
|---|
downloads | Ordered list of DownloadOption objects rendered as buttons |
video | Array containing the primary video CDN URL (first element of downloads) |
images | Present only for slideshow posts; array of image CDN URLs |
music | Audio CDN URL for the background track |
cover | Cover image CDN URL, proxied through /api/proxy-image |
desc | Video title/description text from TikWM |
author.nickname | TikTok username, used in generated download filenames |
author.avatar | Author profile picture CDN URL |
Animations
Framer Motion is used in three places within Home.tsx and VideoResult.tsx:
- Hero block —
initial={{ opacity: 0, y: -20 }} fades and slides the logo and headline in on mount.
- Input area —
initial={{ opacity: 0, scale: 0.95 }} with a delay: 0.2 scales the form in after the hero.
- VideoResult card — wrapped in
AnimatePresence so it animates in (opacity: 0, y: 30 → visible) when data is set and animates out when cleared.
The mode state variable ('video' | 'audio') drives the toggle button’s active styling but does not currently filter the download options returned by the API. The visual toggle is in place as a UI affordance for a future audio-only filter.