A full-stack streaming website with a Next.js 16 + TypeScript frontend and
built-in API routes for scraping β featuring a sleek red & blue glassmorphism design.
Deploy to Vercel in one click.
- Architecture
- Quick Start
- Deploy to Vercel
- Scraper API
- Next.js Frontend
- Environment Variables
- Tech Stack
- Project Structure
- License
A single Next.js 16 project containing both the frontend (SSR pages) and the scraper API (serverless functions).
ββββββββββββββββββββββββββββββββββββββββ
β Next.js 16 + TS β
β β
β src/app/page.tsx ββfetchβββΆ /api/β
β (SSR + ISR) β β
β β β
β βββββββββββββΌββββββββ β
β β API Routes β β
β β (serverless) β β
β β cheerio + axios β β
β βββββββββββ¬ββββββββββ β
βββββββββββββββββββββββββββΌβββββββββββββ
βΌ
ββββββββββββββββββββ
β javtiful.com β
ββββββββββββββββββββ
Everything lives at the project root β no subdirectories, no separate scraper server. The scraper logic is embedded as Next.js API routes (src/app/api/*). Deploy the entire app to Vercel with a single push.
- Node.js 18+ and npm 9+
git clone https://github.com/zainaqdas/supjav.git
cd supjavnpm install
npm run dev
# β Opens at http://localhost:3000The API routes are included β no separate scraper server needed!
- Push this repo to GitHub
- Go to vercel.com/new and import the repo
- Click Deploy
That's it! Vercel auto-detects Next.js β no root directory or configuration needed.
npx vercel
# Follow the prompts to link and deployThe root vercel.json is pre-configured:
| Setting | Value |
|---|---|
| Framework | nextjs |
| Build command | npm run build |
| Output directory | .next |
| Variable | Value | Required |
|---|---|---|
NEXT_PUBLIC_API_URL |
(leave empty β uses internal /api) |
No |
No environment variables needed. The API routes are self-contained within the Next.js app.
The scraper parses HTML from javtiful.com and exposes a clean JSON REST API at /api/*.
Base URL: http://localhost:3000/api/* (same origin as the frontend)
| Endpoint | Method | Params | Description |
|---|---|---|---|
/api/main |
GET | ?page=1 |
Latest videos (paginated) |
/api/trending |
GET | ?page=1 |
Popular/trending videos (paginated) |
/api/categories |
GET | β | All 21 categories with video counts |
/api/category/:slug |
GET | ?page=1 |
Videos in a category (paginated) |
/api/actresses |
GET | β | All actresses with video counts |
/api/actress/:slug |
GET | ?page=1 |
Videos by an actress (paginated) |
/api/channels |
GET | β | All channels/studios with video counts |
/api/channel/:slug |
GET | ?page=1 |
Videos by a channel (paginated) |
/api/search |
GET | ?q=query&page=1 |
Search videos (paginated) |
| Endpoint | Description |
|---|---|
/api/video/:id |
Full video detail (title, poster, metadata, stream URLs) |
/api/video/:id/:slug |
Same as above, but slug ensures correct route matching |
/api/video/:id/stream |
Lightweight β returns only stream URLs + quality options |
/api/video/:id/comments |
Video comments (AJAX endpoint) |
/api/video/:id/download-link |
Download link (requires X-CSRF-Token header) |
/api/csrf-token |
Get CSRF token for authenticated requests |
| Endpoint | Description |
|---|---|
/ |
Frontend home page |
/api |
API documentation β lists all endpoints and query parameters |
{
"id": "108365",
"slug": "hrsm-146",
"title": "HRSM-146 Video Title",
"url": "https://javtiful.com/video/108365/hrsm-146",
"thumbnail": "https://...",
"previewVideo": "https://...",
"duration": "120 min",
"quality": "HD",
"views": "12.3k",
"timeAgo": "2 days ago",
"badges": ["Uncensored"]
}{
"id": "108365",
"slug": "hrsm-146",
"title": "HRSM-146 Video Title",
"poster": "https://...",
"description": "...",
"keywords": ["keyword1", "keyword2"],
"videoCode": "HRSM-146",
"releaseDate": "2024-01-15",
"qualityOptions": [480, 720, 1080],
"defaultQuality": 720,
"streams": [
{
"url": "https://...cloudflarestorage.com/...mp4?...",
"type": "video/mp4",
"quality": "720p"
}
],
"previewSources": ["https://jav.si/..."],
"thumbnails": ["https://..."],
"actresses": [{ "slug": "actress-name", "name": "Actress Name" }],
"tags": [{ "type": "category", "slug": "amateur", "name": "Amateur" }],
"endpoints": {
"comments": "/video/108365/comments",
"playlist": "/video/108365/playlist",
"downloadLink": "/video/108365/download-link",
"favorite": "/video/108365/favorite",
"report": "/video/108365/report"
}
}Stream URLs are Cloudflare R2 pre-signed URLs valid for approximately 1 hour. They are extracted from the
#frontWatchConfigJSON blob embedded in the source video page.
{
"source": "main",
"page": 1,
"totalPages": 3,
"totalResults": 42,
"videos": [...]
}A modern, responsive streaming website built with Next.js 16, TypeScript, and Tailwind CSS 4.
- Theme: Deep dark backgrounds (
#0a0a0f) with red (#ff4444) and blue (#4488ff) gradient accents - Glassmorphism cards with hover overlays, animated border gradients, and fade-in-up animations
- Custom scrollbar, skeleton shimmer loaders, gradient text, and micro-interactions throughout
- Fully responsive β mobile hamburger nav, adaptive grids
| Page | Route | Description |
|---|---|---|
| Home | / |
Hero section, latest videos grid, trending section |
| Trending | /trending |
Popular videos (paginated) |
| Categories | /categories |
Grid of all 21 categories |
| Category | /category/:slug |
Videos filtered by category (paginated) |
| Actresses | /actresses |
Grid of all actresses |
| Actress | /actress/:slug |
Videos by an actress (paginated) |
| Channels | /channels |
Grid of all channels/studios |
| Channel | /channel/:slug |
Videos by a channel (paginated) |
| Search | /search?q=query |
Search results (paginated) |
| Video | /video/:id/:slug |
Full video page with player, metadata, actresses, tags, thumbnails |
| 404 | /[...] |
Custom not-found page |
| Error | * |
Error boundary with reset button |
| Loading | * |
Skeleton shimmer while pages load |
| Component | Description |
|---|---|
VideoPlayer |
Custom HTML5 player with play/pause, progress bar, volume, quality selector, fullscreen, auto-hide controls |
VideoCard |
Glassmorphism card with hover preview, quality/duration badges, gradient overlays |
PreviewVideo |
'use client' sub-component for mouse-enter preview playback |
VideoGrid |
Responsive CSS grid with staggered fade-in-up animations |
Navbar |
Sticky nav with scroll blur, mobile hamburger menu, inline search |
Footer |
Links and branding |
Pagination |
'use client' component with prev/next, page numbers, dots, gradient active state |
SectionHeader |
Gradient-text section titles with optional "View All" link |
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_API_URL |
/api |
Base URL for scraper API. Leave empty for internal API routes. |
- Next.js 16 β React framework with SSR, ISR, App Router, API routes
- React 19 β UI library
- TypeScript 5 β Type safety
- Tailwind CSS 4 β Utility-first CSS
- hls.js β HLS stream playback
- axios β HTTP client for scraping
- cheerio β jQuery-style HTML parsing
A single, consolidated Next.js project at root β no subdirectories, no separate servers.
supjav/
βββ package.json # Single package.json for everything
βββ next.config.ts # Next.js configuration
βββ tsconfig.json # TypeScript configuration
βββ vercel.json # Vercel deployment config (framework: nextjs)
βββ postcss.config.mjs # PostCSS / Tailwind config
βββ eslint.config.mjs # ESLint configuration
βββ .gitignore
βββ README.md
βββ public/ # Static assets (favicons, SVGs)
βββ src/
βββ app/
β βββ globals.css # Global styles & Tailwind imports
β βββ layout.tsx # Root layout (theme, fonts, metadata, force-dynamic)
β βββ page.tsx # Home page (hero, featured, latest, trending)
β βββ loading.tsx # Global loading skeleton (shimmer)
β βββ error.tsx # Error boundary with reset
β βββ not-found.tsx # Custom 404 page
β βββ favicon.ico
β βββ trending/
β β βββ page.tsx # /trending
β βββ categories/
β β βββ page.tsx # /categories
β βββ category/
β β βββ [slug]/
β β βββ page.tsx # /category/:slug
β βββ actresses/
β β βββ page.tsx # /actresses
β βββ actress/
β β βββ [slug]/
β β βββ page.tsx # /actress/:slug
β βββ channels/
β β βββ page.tsx # /channels
β βββ channel/
β β βββ [slug]/
β β βββ page.tsx # /channel/:slug
β βββ search/
β β βββ page.tsx # /search?q=query
β βββ video/
β β βββ [id]/
β β βββ [slug]/
β β βββ page.tsx # /video/:id/:slug (player, metadata, related)
β βββ api/ # β All scraper endpoints (serverless on Vercel)
β βββ route.ts # API docs (GET /api)
β βββ main/route.ts # GET /api/main?page=1
β βββ trending/route.ts # GET /api/trending?page=1
β βββ categories/route.ts # GET /api/categories
β βββ category/[slug]/route.ts # GET /api/category/:slug?page=1
β βββ actresses/route.ts # GET /api/actresses
β βββ actress/[slug]/route.ts # GET /api/actress/:slug?page=1
β βββ channels/route.ts # GET /api/channels
β βββ channel/[slug]/route.ts # GET /api/channel/:slug?page=1
β βββ search/route.ts # GET /api/search?q=query&page=1
β βββ video/[id]/route.ts # GET /api/video/:id
β βββ video/[id]/[slug]/route.ts # GET /api/video/:id/:slug
β βββ video/[id]/stream/route.ts # GET /api/video/:id/stream
β βββ video/[id]/comments/route.ts # GET /api/video/:id/comments
β βββ video/[id]/download-link/route.ts # GET /api/video/:id/download-link
β βββ csrf-token/route.ts # GET /api/csrf-token
βββ components/
β βββ Navbar.tsx # Sticky nav with scroll blur, mobile hamburger, search
β βββ Footer.tsx # Links and branding
β βββ VideoCard.tsx # Glassmorphism card with hover overlay & badges
β βββ PreviewVideo.tsx # 'use client' mouse-enter preview playback
β βββ VideoPlayer.tsx # Custom player: play/pause, volume, quality, fullscreen
β βββ VideoGrid.tsx # Responsive grid with fade-in-up animations
β βββ Pagination.tsx # 'use client' prev/next, page numbers, gradient active
β βββ SectionHeader.tsx # Gradient-text titles with optional "View All" link
βββ lib/
βββ api.ts # Centralized API client (fetch wrapper with ISR caching)
βββ types.ts # TypeScript interfaces (VideoResult, VideoDetail, etc.)
βββ scraper.ts # Core scraping engine (TypeScript ESM, cheerio + axios)
MIT Β© zainaqdas
Built with β€οΈ using Next.js, cheerio, and TypeScript