U
Utilities
ToolsBlogAboutGet Started
Building an Immersive Swipe Experience: A TikTok-like Random Girl Content Browser with Next.js

Building an Immersive Swipe Experience: A TikTok-like Random Girl Content Browser with Next.js

Published onFriday, October 17, 2025
12 min read
Next.jsSwiperZustandTailwind CSSLucide Reactimmersive experienceswipe interactionvideo browsercontent browserTikTok-likerandom contentauto-playerror handlingplayback modesweb developmentfrontendreacttypescriptindie developerindie hackerproductivity toolminimalist toolvideo playerresponsive designuser experiencestate management
Back to List

Building an Immersive Swipe Experience: A TikTok-like Random Girl Content Browser with Next.js

Introduction

Imagine an app where users can swipe up and down to browse a variety of stylish photos and videos of girls, delivering a TikTok-like immersive experience. Today, I’ll walk you through building an addictive random girl content browser from scratch. This app features smooth swipe interactions, auto-play, smart error handling, and multiple playback modes, ensuring users enjoy high-quality content effortlessly.

Live Demo: This app is deployed at https://immersiview.utities.online/ as part of the utility toolkit www.utities.online. You can check it out right now!

Random Girl Content Browser Demo


Tech Stack

Before diving into the code, let’s outline the core technologies we’ll use:

  • Framework: Next.js 13+ (App Router)
  • UI Swipe Component: Swiper
  • State Management: Zustand
  • Styling: Tailwind CSS
  • Icons: Lucide React

Step 1: Create the Base Route Component

First, we need to create the entry page for our app. This page is minimal and primarily renders our core video swipe component.

tsx
// app/random-girl-videos/page.tsx import VideoSlider from "../components/VideoSlider"; // Client-side renderer for random girl videos export default function RandomVideoRenderer() { // Simple and clean: render the video slider component return ; }

This entry point delegates all core logic to the VideoSlider component, keeping the codebase clean and modular.


Step 2: Implement Immersive Experience Core – Scroll Lock

To fully immerse users in the video content, we need to disable page scrolling. We’ll create a custom hook for this:

tsx
// hooks/useBodyScrollLock.ts import { useEffect } from 'react'; /** * Custom hook to control body scroll behavior. * Disables body scroll on mount, restores it on unmount. */ export function useBodyScrollLock() { useEffect(() => { // Save the original overflow value const originalOverflow = document.body.style.overflow; // Disable body scroll document.body.style.overflow = 'hidden'; // Cleanup: Restore the original overflow value return () => { document.body.style.overflow = originalOverflow; }; }, []); }

This simple hook is crucial for creating an immersive experience by temporarily modifying the document.body.style.overflow property to prevent page scrolling, keeping the user focused on the current video.


Step 3: Build the Video Slider Core Component

The VideoSlider component is the heart of the app, managing video loading, display, and interaction logic. Here’s how to implement it:

tsx
// app/components/VideoSlider.tsx "use client"; import { useState, useCallback, useEffect } from "react"; import { RefreshCw } from "lucide-react"; import { useBodyScrollLock } from "../../hooks/useBodyScrollLock"; import { Swiper, SwiperSlide } from "swiper/react"; import { Keyboard } from "swiper/modules"; import type { Swiper as SwiperType } from "swiper"; import { useGalleryStore } from "../../lib/store"; import { VideoWithLoading } from "./VideoWithLoading"; import { getApiRandomVideos, getDomain } from "../utils/imageUtils"; import type { VideoData } from "../utils/imageUtils"; import "swiper/css"; export default function VideoSlider() { const [currentIndex, setCurrentIndex] = useState(0); const [swiperInstance, setSwiperInstance] = useState(null); const [displayVideos, setDisplayVideos] = useState(getApiRandomVideos(5)); const [videoErrors, setVideoErrors] = useState>(new Map()); const [blockedDomains, setBlockedDomains] = useState>(new Set()); const { videoPlayMode } = useGalleryStore(); // Lock body scroll for immersive experience useBodyScrollLock(); // Fetch new random girl videos const handleRandom = useCallback(() => { // Clean up old video instances to avoid memory leaks const videoElements = document.querySelectorAll("video"); videoElements.forEach((video) => { const htmlVideo = video as HTMLVideoElement; htmlVideo.pause(); htmlVideo.src = ""; htmlVideo.load(); // Release resources }); // Get a new set of girl videos const newVideos = getApiRandomVideos(5); setDisplayVideos(newVideos); setCurrentIndex(0); // Ensure swiper slides to the first video if (swiperInstance) { swiperInstance.slideTo(0, 0); } }, [swiperInstance]); // Handle video loading errors and block problematic domains const handleVideoError = useCallback( (video: VideoData) => { setVideoErrors((prev) => { const newErrors = new Map(prev); const domain = getDomain(video.url || ""); const currentErrors = newErrors.get(domain) || 0; newErrors.set(domain, currentErrors + 1); // Block domains with more than 3 errors if (currentErrors + 1 >= 3 && !blockedDomains.has(domain)) { setBlockedDomains((prevDomains) => new Set(prevDomains).add(domain)); console.log(`Domain ${domain} blocked due to repeated video load failures`); } return newErrors; }); // Auto-switch to the next video after an error setTimeout(() => { if (swiperInstance) { swiperInstance.slideNext(); } }, 2000); }, [swiperInstance, blockedDomains] ); // Filter out videos from blocked domains const filteredVideos = displayVideos.filter((video) => { try { const domain = getDomain(video.url || ""); return !blockedDomains.has(domain); } catch { return true; } }); // Handle video end based on playback mode const handleVideoEnded = useCallback(() => { if (!swiperInstance) return; switch (videoPlayMode) { case "random": // Random mode: Generate new videos when reaching the end if (currentIndex === filteredVideos.length - 1) { handleRandom(); } else { swiperInstance.slideNext(); } break; case "loop": // Loop mode: Loop through the current video list if (currentIndex === filteredVideos.length - 1) { swiperInstance.slideTo(0); } else { swiperInstance.slideNext(); } break; } }, [currentIndex, swiperInstance, filteredVideos.length, videoPlayMode, handleRandom]); // Keyboard shortcut support useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key.toLowerCase() === "r") { handleRandom(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [handleRandom]); return (
{/* Header and refresh button */}

Random Girl Videos

{/* Swiper for swipe effect */} { setCurrentIndex(swiper.activeIndex % filteredVideos.length); setSwiperInstance(swiper); }} onInit={(swiper) => setSwiperInstance(swiper)} className="h-full w-full" slidesPerView={1} > {filteredVideos.map((video) => ( {}} onError={() => handleVideoError(video)} isActive={filteredVideos.indexOf(video) === currentIndex} onVideoEnded={handleVideoEnded} /> ))} {/* Bottom info */}

{filteredVideos[currentIndex]?.title || "Loading..."}

); }

Key Features:

  • Smart Error Handling: Tracks errors per domain and blocks problematic sources for a seamless experience.
  • Multiple Playback Modes: Supports both random and loop modes to cater to different user preferences.
  • Resource Management: Properly releases old video resources to prevent memory leaks.
  • Keyboard Interaction: Supports the "R" key to refresh videos, enhancing desktop usability.

Step 4: Build the Video Player Component with State Management

The VideoWithLoading component handles individual video playback, loading states, and user interactions:

tsx
// app/components/VideoWithLoading.tsx import { useState, useRef, useEffect } from "react"; import { AlertCircle, Play } from "lucide-react"; import type { VideoData } from "../utils/imageUtils"; interface VideoWithLoadingProps { video: VideoData; onLoad: () => void; onError: () => void; isActive: boolean; onVideoEnded?: () => void; } export const VideoWithLoading = ({ video, onLoad, onError, isActive, onVideoEnded }: VideoWithLoadingProps) => { const [hasError, setHasError] = useState(false); const [status, setStatus] = useState<"idle" | "loading" | "error" | "playing" | "finished">("idle"); const videoRef = useRef(null); // Handle video load completion const handleLoadedData = () => { setStatus("playing"); onLoad(); }; // Handle video load errors const handleError = () => { setHasError(true); setStatus("error"); onError(); }; // Handle video click for play/pause const handleVideoClick = () => { if (!videoRef.current) return; if (videoRef.current.paused) { videoRef.current.play().then(() => { setStatus("playing"); }).catch((err) => { console.log("Playback failed on click:", err); if (err.name === 'NotAllowedError') { setStatus("error"); } else { setHasError(true); onError(); } }); } else { videoRef.current.pause(); setStatus("idle"); } }; // Handle video end const handleEnded = () => { setStatus("finished"); if (onVideoEnded) { onVideoEnded(); } }; // Auto-play or pause based on active state useEffect(() => { const videoElement = videoRef.current; if (!videoElement) return; videoElement.muted = false; videoElement.playsInline = true; // Inline playback on mobile if (isActive) { videoElement.play().then(() => { setStatus("playing"); }).catch((err) => { console.log("Auto-play prevented, user interaction required"); setStatus("idle"); }); } else { videoElement.pause(); setStatus("finished"); } return () => { if (videoElement) { videoElement.pause(); } }; }, [video.url, isActive]); // Fallback UI for error state if (hasError) { return (

Video failed to load

); } return (
{/* Video element */} {/* Loading indicator */} {status === "loading" && (
)} {/* User interaction prompt */} {status === "idle" && (

Click to play video

)}
); };

Key Features:

  • State Machine: Uses a status variable to clearly manage video states (idle, loading, error, playing, finished).
  • Smart Error Handling: Distinguishes between browser policy restrictions and actual video load errors.
  • Responsive Interaction: Displays different UI feedback based on the video’s current state.
  • Auto-Play Strategy: Only attempts auto-play for the currently visible video, conserving resources and complying with browser policies.

Step 5: Implement State Management and Data Fetching

To remember user playback preferences, we use Zustand for state management:

tsx
// lib/store.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; export type VideoPlayMode = 'random' | 'loop'; interface GalleryState { videoPlayMode: VideoPlayMode; setVideoPlayMode: (mode: VideoPlayMode) => void; } export const useGalleryStore = create()( persist( (set) => ({ videoPlayMode: 'random', // Default to random mode setVideoPlayMode: (mode) => set({ videoPlayMode: mode }), }), { name: 'gallery-storage' } // Local storage key ) );

We also implement utility functions for fetching video data:

tsx
// app/utils/imageUtils.ts export interface VideoData { id: number; url: string; title: string; } // Fetch random girl videos from API export const getApiRandomVideos = (count: number = 4) => { const apiUrl = "http://api.mmp.cc/api/ksvideo?type=mp4&id=jk"; return Array.from({ length: count }, (_, index) => ({ id: index, url: `${apiUrl}&t=${Date.now()}-${index}`, // Add timestamp to avoid caching title: `Girl Video ${index + 1}`, })); }; // Extract domain for error handling and blocking export const getDomain = (url: string): string => { try { return new URL(url).hostname; } catch { return "unknown"; } };

These utilities handle data fetching, cache control, and domain extraction, supporting smart error handling.


Deployment and Access

This app is deployed at https://immersiview.utities.online/ as part of the utility toolkit www.utities.online. You can experience the smooth video browsing app right away!

Deployment is straightforward. Use Vercel, Netlify, or any platform that supports Next.js. Simply connect your code repository, and the platform will automatically build and deploy your app.


Summary and Outlook

In this tutorial, we built an immersive random girl video browser with the following core features:

  • Smooth vertical swipe transitions for browsing different girl content
  • Smart video loading and error handling for a seamless experience
  • Multiple playback modes to suit different browsing habits
  • Responsive UI that works perfectly on all devices
  • Keyboard interaction support for desktop users
  • State management to remember user preferences

The app’s core appeal lies in its ability to provide a relaxed and enjoyable browsing experience, with each swipe revealing new and exciting content. As a developer, you can further expand this app by adding social features like likes, favorites, and sharing, or by optimizing the content recommendation algorithm to help users discover their preferred types of content more accurately.

Now that you’ve mastered the key techniques for building immersive video apps, you can apply these skills to more creative projects. Happy coding!

U
utities.online

Powerful and easy-to-use online tools to make your work more efficient.

Popular Tools

Logo MakerVideo SplitterImage Compare ProImmersiview

Company

About UsContact UsPrivacy PolicyTerms of Service

Subscribe to Updates

Get the latest tools and feature updates

© 2024 utities.online. All rights reserved.