moviexyz is a website I built for people who have trouble choosing a movie to watch. My motivation for building this was to apply and improve my skills as a front-end developer and break out of tutorial hell by building something on my own, from start to finish.
Check out the Live Website
The Layout.tsx
component uses SWR to randomly fetch movies from the TMDb API. It does this by making a call to the api/randomId
endpoint on the client-side. If the user selected any movie genres, the selected genre ID list is passed as a parameter to the endpoint.
The getRandomMovieId
utility function is called within the endpoint, which takes the genre ID list from the query parameters and returns a random movie ID. The returned movie ID is then used as the button href within a Link component to navigate the user to the fetched movie's page.
When the button is clicked, the handleClick
function is called, which updates the loading state and uses the mutate
method from the useSWR
hook to update the current random ID.
The movie page is rendered on the server-side mainly because I wanted to use the movie data to generate dynamic Open Graph (OG) images using @vercel/og
.
The page uses getServerSideProps
to fetch the movie data on the server-side by calling getMovieDetails
, passing the dynamic query ID as an argument, and returning the movie data as props to the MoviePage
component.
The MoviePage
component then uses the movie data in the Seo
and MovieCard
components. The ogContent
property of the Seo
component is used to generate a dynamic URL that points to a serverless function that generates an OG image for the movie. This is achieved using the @vercel/og
library, which generates OG images based on the content of a given URL.
The useGenresState
hook manages the genre state using Zustand to create a store that persists the genre ID list data in local storage.
The GenresToggle.tsx
component renders a toggle group by mapping through an array of objects that contain the genre name and its respective ID, specified in the TMDb API docs. The handleValueChange
function is passed to the onValueChange
property of the toggle group, which calls the setGenreIdList
method from the custom hook to update the genre ID list state.
In utils/tmdb.ts
, there are two utility functions for fetching movie data using moviedb-promise
to interact with the TMDb API.
The getRandomMovieId
function takes a parsed genresId
string as an argument, generates a random number between 1 and 500 to use as the random page, passes the pages and genres as options to the discoverMovie
method, filters the page result movies to get movies with the required fields, and then returns a random movie ID based on the length of the filtered array.
The getMovieDetails
function takes a movie ID as an argument, calls the movieInfo
method passing the ID and the append_to_response
option with videos and recommendations, and extends the return type to include those. The returned values are parsed to ensure fallback values, correct recommendation array size, and required fields.
There's a toggle that allows the user to choose the letter case of the website to either normal, capitalized text or all lowercase text. This is also handled using zustand and local storage, definetely unnecessary feature that took me way too long to build.
Other than the dynamic og image for movie pages, there's also a default-card, which is not dynamic. Both cards were styled using tailwind, you can see the code in src/pages/api/og/
.
The project was bootstrapped with create-t3-app.
Final Thoughts
Most of the stuff I've used to build this website was new to me, so I had to learn it and figure out how to apply to my use case, therefore, I had a bit of a steep learning curve there, and this took some time. I feel like this could probably have been achieved in simpler, more performant ways, but for what it's worth, I did learned a lot of stuff building this and had a lot of fun figuring things out and getting everything to work the way I wanted it to.
I'm always looking for ways to improve my skills and learn new things, so if you have any feedback or suggestions, I'd love to hear them! Thanks for checking out the project - I hope you enjoyed it as much as I enjoyed building it.