After spending a lot of time building full-stack and backend-heavy projects, I wanted to work on something that was unapologetically frontend-focused, something that pushed me to think more about user experience, loading states, performance, and how to make an application feel polished rather than just functional.
That’s how Movix came about.
Movix is a Netflix-inspired movie discovery app built with React, TypeScript, React Query, Tailwind CSS, and Framer Motion. The goal wasn’t just to fetch movie data from the TMDB API and render cards. I wanted to build an experience that felt smooth to use, with fast search, responsive interactions, useful details, and small touches like prefetching and animated hover states that make the app feel more intentional.
Live Demo: https://movix-tmdb.netlify.app
GitHub: https://github.com/ankita007-coder/TMDB-Movie-Explorer
What Movix does
Movix lets users:
- browse trending, popular, and top-rated movies
- search for movies and actors
- open detailed movie pages with cast information
- view actor details in a modal
- save movies to a personal watchlist
- navigate a UI inspired by modern streaming platforms
Under the hood, it also became a good playground for exploring React Query caching, prefetching strategies, Context-based state management, and performance-focused frontend architecture.
The idea behind the project
A lot of movie apps are basically the same pattern:
- fetch a list
- render some cards
- show a details page
There’s nothing wrong with that, but I wanted this project to focus on how the app feels as much as what it does.
That meant thinking about questions like:
- How do I reduce loading friction when a user clicks into a movie?
- How should search behave so it doesn’t feel clunky?
- What’s the best way to handle watchlist state without overengineering it?
- How can animations add polish without becoming distracting?
So instead of treating Movix like “just another React project,” I used it to explore the frontend decisions that make an application feel more complete.
Tech stack
Frontend
- React
- TypeScript
- Vite
Styling and UI
- Tailwind CSS
- Framer Motion
Data fetching and server state
- TanStack Query
State management
- React Context API
Authentication
- Auth0
API
- TMDB (The Movie Database)
Building the home page around discovery
The home page is designed around movie discovery rather than just raw data display.
It includes:
- a hero carousel featuring trending movies
-
multiple horizontal sections for:
- Trending
- Popular
- Top Rated
This structure let me build a reusable section-based layout, where each category uses the same UI pattern but fetches different data.
That might sound small, but I wanted the project structure to feel scalable from the start. Instead of writing one-off code for every section, I built reusable pieces like:
HorizontalMovieSectionMovieCard- shared loading skeletons
- reusable API hooks for different categories
That kept the UI layer much cleaner as the project grew.
Designing movie cards that reveal more context
A movie card usually starts as “poster + title,” but that felt too static.
So I added hover interactions where cards reveal additional information like:
- runtime
- genres
- movie title overlay
These interactions were built using Framer Motion, which made it easy to add animations without turning the code into a mess of CSS transitions and state toggles.
The goal here wasn’t to make the UI flashy for the sake of it, it was to make browsing feel more interactive and less like scanning a spreadsheet of posters.
Using React Query to make navigation feel faster
One of the parts I enjoyed most in this project was using React Query prefetching to improve perceived performance.
When a user hovers over a movie card, the app prefetches the movie details query in the background. So if they click into that movie a second later, there’s a good chance the details are already cached and ready.
That small decision makes the app feel much faster than if every detail page waited for a fresh fetch after navigation.
Why React Query worked well here
Movix has a lot of server-driven data:
- trending movies
- popular movies
- top-rated movies
- search results
- movie details
- cast information
- actor details
- known-for credits
Managing all of that with manual loading flags and useEffect chains would get messy pretty quickly.
React Query gave me a much cleaner model:
- built-in caching
- stale time control
- query deduplication
- background refetching
- easier loading/error handling
For this project, I configured queries with a 5-minute stale time and disabled unnecessary refetches on window focus to keep the UX smoother.
Search: simple feature, lots of UX details
Search ended up being one of those features that seems straightforward until you start thinking about edge cases.
Movix supports searching for both:
- movies
- actors
To make it feel usable, I added:
- debounced input
- loading skeletons
- empty states
- error handling
Without debouncing, every keystroke would trigger a new request and the whole experience would feel noisy. Debouncing gave the search a much calmer, more deliberate feel while also reducing unnecessary API calls.
Building the movie details page
The movie details page pulls together a lot of information in one place:
- backdrop and poster
- title and tagline
- runtime
- genres
- release date
- overview
- rating
- cast details
- streaming providers (India)
This page became a good exercise in component composition because there’s a lot of data, but I still wanted the UI to feel structured rather than overwhelming.
So instead of building one huge component, I split the page into smaller pieces like:
- movie hero/details section
- cast section
- reusable person cards
- modal-based actor details
That made the page much easier to reason about and update.
The cast modal and “Known For” section
One of my favorite features in Movix is the actor modal.
Clicking on a cast member opens a modal that shows:
- actor photo
- name
- department
- short biography
- movies they’re known for
The “Known For” section is built using TMDB’s combined credits endpoint and then filtered/sorted to show the most relevant movie work.
I liked this feature because it made the movie details page feel less static. Instead of cast members being dead-end data, they became entry points into another layer of discovery.
Watchlist without overengineering state
For the watchlist, I deliberately kept the state management lightweight.
I used:
- React Context for shared watchlist state
- localStorage for persistence
Each authenticated user gets a unique storage key, which means their watchlist remains separate.
This was one of those places where I didn’t want to overcomplicate the solution. A global state library would have been unnecessary here. Context + localStorage was enough, and it kept the feature simple.
Performance optimizations I cared about
Movix wasn’t just about UI, I also wanted it to be reasonably efficient.
A few things I focused on:
1. Prefetching movie details
Hovering over a movie card triggers a prefetch, which reduces perceived load time on the details page.
2. Query caching
React Query handles caching and deduplication, which avoids unnecessary repeated requests.
3. Lazy loading images
Movie posters and other media are loaded lazily where appropriate.
4. Route-based code splitting
Pages are lazy-loaded using React.lazy() to reduce the initial bundle size.
None of these optimizations are groundbreaking individually, but together they make the app feel noticeably smoother.
Project structure
As the project grew, I wanted to avoid dumping everything into a flat components folder.
So I split the code into a more structured layout:
src
├── api/
│ └── movieService.ts
├── components/
│ ├── movie/
│ │ ├── MovieCard
│ │ ├── HorizontalMovieSection
│ │ ├── CastSection
│ │ └── PersonCard
│ └── reusable/
│ ├── Container
│ ├── Skeleton
│ └── Modal
├── context/
│ └── WatchlistContext
├── hooks/
│ ├── useTrending
│ ├── useTopRated
│ ├── usePopular
│ ├── useMovieDetails
│ ├── usePersonCredits
│ └── usePersonDetails
├── pages/
│ ├── Home
│ ├── MovieDetailsPage
│ ├── SearchPage
│ └── WatchlistPage
├── routes/
│ └── AppRoutes
├── types/
│ └── movie.ts
└── utils/
This separation helped keep concerns clearer:
- API logic stays in one place
- data-fetching hooks stay reusable
- components stay focused on UI
- pages assemble the bigger experience
What this project taught me
Movix taught me a lot about the difference between building a feature and building an experience.
Technically, the project helped me get more comfortable with:
- React Query for server-state management
- prefetching strategies
- organizing frontend code for scale
- balancing animation with usability
- choosing simpler state management when it’s enough
But more importantly, it reminded me that frontend work isn’t just about rendering data. It’s about making the product feel intuitive, responsive, and enjoyable to use.
A movie app is a pretty familiar project idea, so the interesting part wasn’t the domain itself, it was the opportunity to think carefully about performance, interaction design, and how small decisions add up to a better experience.
What I’d improve next
If I continue working on Movix, I’d like to add:
- infinite scrolling for search results
- more advanced filters by genre/year/language
- a dark/light theme toggle
- better recommendations based on watchlist history
- stronger accessibility improvements across interactive components
Final thoughts
Movix started as a movie discovery app, but it turned into a really useful frontend playground for experimenting with React Query, performance optimization, reusable architecture, and UI polish.
It was also a good reminder that “frontend project” doesn’t have to mean “just styling things.” There’s a lot of engineering depth in figuring out how to fetch, cache, structure, and present data in a way that feels seamless to the user.
If you’ve built something similar, or if you’d approach React Query caching/prefetching differently, I’d love to hear how you think about it.
Live Demo: https://movix-tmdb.netlify.app
GitHub: https://github.com/ankita007-coder/TMDB-Movie-Explorer