From API to UI: Building a SpaceX Launch Tracker with NextJs and Tailwind

0 %
Gervis Bermudez
Front-end Developer
Ui/UX Designer
  • Residence:
    Argentina
  • City:
    Buenos Aires
  • Age:
    30
English
React / Redux
Node / Typescript
PHP / Laravel
Figma / Xd
mongodb / Firebase
  • React, Angular, Vue
  • Stylus, Sass, Less
  • Gulp, Webpack, Grunt
  • GIT knowledge

From API to UI: Building a SpaceX Launch Tracker with NextJs and Tailwind

24 Mar 2025

I everyone, I recently undertook a fascinating project: building a SpaceX launch tracker using Next.js and Tailwind CSS. The aim was to create a clean, responsive application that pulls data from the SpaceX API, enabling users to explore launch information and curate their favorite launches. This project provided a fantastic opportunity to delve deeper into Next.js's app router and the utility-first approach of Tailwind. Let's explore the development process and some key insights gained.

The technical test:

FRONTEND EXERCISE
The exercise consists of building an application to view Spacex’s latest launches, and being able to add them to a “favourite” list to revisit them later.
The application should be developed using React 16.8.4 or further.

Expectations
On the first app load the candidate should hit the following endpoints from SpaceX to retrieve information about rockets and launches:

https://api.spacexdata.com/v3/rockets https://api.spacexdata.com/v3/launches

Information about API usage can be found here:
https://docs.spacexdata.com

The candidate should combine the information to get a result similar to the following:

{
	flight_number: 39,
	mission_name: "NROL-76",
	launch_date_unix: 1493637300,
	details: "launch details",
	mission_patch: <image_URL>,
	rocket: {
		rocket_id: "falcon1",
		rocket_name: "Falcon 1",
		active: true,
		cost_per_launch: 6700000,
		company: "SpaceX"
	}
}

This is a list of functionality the candidate needs to include in the app order by priority,
there’s no need to do everything, let’s include as much as you can:

  • Fetch endpoints and merge arrays (we need to populate the rocket data of the
    launches with the rockets array)
  • Display launches
  • Favourite functionality (persist them on local storage)
  • Search by mission name
  • Inspect a specific launch detail
  • Pagination

Wireframes: SpaceX Figma

Let's break down how we'll implement this SpaceX launch tracker using NextJS and Tailwind CSS. We'll follow the wireframes as a guide while adding our own modern touches to create an engaging user experience. Our focus will be on building a responsive, performant application that makes it easy to explore and track SpaceX launches.

Setting Up the Foundation - Tech Stack and Initial Steps


I started by setting up a fresh Next.js project with TypeScript and ESLint, following the official documentation. Then came the integration of Tailwind CSS, which proved to be incredibly efficient for styling. I extended the Tailwind theme to match the color palette specified in the Figma wireframes, ensuring a consistent and visually appealing design. For custom fonts, I leveraged Next.js's localFont package to include the D-DIN font family.

Let's start from the beginning, Installing the NextJs and Tailwind with dependencies, as it’s described by the documentation in

https://nextjs.org/docs/app/getting-started/installation

https://tailwindcss.com/docs/installation/framework-guides/nextjs

 npx create-next-app@latest spacex-launches-webapp --typescript --eslint --app
 cd spacex-launches-webapp

At this point we get the initial scaffolding of a nextjs app, so the next steps is install tailwind

npm install tailwindcss @tailwindcss/postcss postcss

Component-Driven UI Development

I adopted a component-driven architecture, building reusable components like buttons, icons, paginators, and tabs. This approach not only made the code more maintainable but also accelerated the UI development process. Tailwind's utility classes were instrumental in creating a clean and responsive design, allowing me to translate the Figma wireframes into reality efficiently. I paid close attention to the details of the UI, to make it as close as possible to the figma design, and still keep it responsive

Updating the tailwind config json and add the colors describe in the figma components to maintain an consisten palete color - theme:

colors in figma

The tailwind theme result:

 colors: {
      "app-bg": "#121212",
      "app-surface": "#2C2C2E",
      "app-white": "#ffffffde",
      "app-gray": {
        100: "rgba(255, 255, 255, 0.77)",
        200: "rgba(255, 255, 255, 0.67)",
        300: "rgba(255, 255, 255, 0.57)",
        400: "rgba(255, 255, 255, 0.47)",
        500: "rgba(255, 255, 255, 0.37)",
        600: "rgba(255, 255, 255, 0.27)",
        700: "rgba(255, 255, 255, 0.17)",
      },
      "app-dp": {
        100: "rgba(30, 30, 30, 1)",
        200: "rgba(35, 35, 35, 1)",
        600: "rgba(63, 63, 63, 1)",
        280: "rgba(70, 70, 70, 1)",
        300: "rgba(75, 75, 75, 1)",
      },
    },
    extend: {
      backgroundImage: {
        "app-gradient": "linear-gradient(180deg, #121212 64.11%, #1E1E1E 100%)",
        "detail-gradient": "linear-gradient(0deg, #00000075, #ffffff26)",
      }



To implement the fonts specified in the Figma design, use the Next.js localFont package as shown below:

fonts in figma
fonts in figma

Son we need to set the D-Din Regular and Bold font, so we are going to use the nextjs font package, as following:

import localFont from "@next/font/local";

// Font files can be colocated inside of `assets`

const dDinFont = localFont({
  src: [
    {
      path: "../assets/fonts/d-din/D-DIN.woff2",
      weight: "400",
      style: "normal",
    },
    {
      path: "../assets/fonts/d-din/D-DIN-Bold.woff2",
      weight: "600",
      style: "normal",
    },
  ],
});


We have to define the global context to manage and transversal state, here we have the minimum defined stated that the app need to works as MVP:

export interface LaunchesContextProps {
  launches: Launch[];
  paginatedLaunches: Launch[];
  setLaunches: React.Dispatch>;
  currentPage: number;
  setCurrentPage: React.Dispatch>;
  totalPages: number;
  isLoading: boolean;
  favorites: Launch[];
  setFavorites: React.Dispatch>;
  addFavorite: (launch: Launch) => void;
  removeFavorite: (flightNumber: number) => void;
}

const LaunchesContext = createContext(
  undefined
);

export const useLaunches = () => {
  const context = useContext(LaunchesContext);
  if (!context) {
    throw new Error("useLaunches must be used within a LaunchesProvider");
  }
  return context;
};

API Integration and Data Management: 🚀

A crucial part of the project was fetching and merging data from the SpaceX API. I created a dedicated fetchLaunches function with flexible query parameters to handle various API requests. To manage the application's state, I implemented a global context using React's useContext hook. This allowed for seamless data sharing across components, including launch data, pagination, and favorites.

I defined and single function to access to the api of SpaceX:

export const fetchLaunches = async (
  options: FetchLaunchesOptions = {}
): Promise<FetchLaunchesResponse> => {
  const { id, limit, offset, sort, order } = options;
  const queryParams = new URLSearchParams();

  if (id !== undefined) queryParams.append("id", String(id));
  if (limit !== undefined) queryParams.append("limit", String(limit));
  if (offset !== undefined) queryParams.append("offset", String(offset));
  if (sort !== undefined) queryParams.append("sort", sort);
  if (order !== undefined) queryParams.append("order", order);

  const queryString = queryParams.toString();
  const url = `${config.API_URL}/launches${
    queryString ? `?${queryString}` : ""
  }`;

  try {
    const response = await fetch(url);
    const data: Launch[] = await response.json();
    const totalPages = Math.ceil(190 / (limit ?? 1));
    return { launches: data, totalPages };
  } catch (error) {
    console.error("Failed to fetch launches", error);
    throw error;
  }
};

One of the main challenges was handling API errors gracefully and implementing robust loading states. I added error handling to the fetchLaunches function and implemented loading indicators to provide a smooth user experience. Additionally, I focused on optimizing the pagination logic and search functionality to ensure efficient data retrieval and filtering. Managing local storage for favorites was another key aspect, requiring careful serialization and deserialization of data.

Components

I develop the commons components for this app, that includes Buttons, Icons, Paginator, Tabs, ect.

The components

At this point we have everything that we need to start the UI development, so I define 2 main pages: Home with the list of Launches that we obtain from the API with a search input to filter the results, pagination and two tabs with results and favorites and the Details Page to show the details of the Rocket of the Launch selected.

Using Tailwind to define the UI

The most important thing I learned this time is just get fun while use predefined tailwind classes to develop the UI of the app, it’s fun and easy (Once you understand it lol), for example look at how I define the background defined on theme config:

return (
    <header className="relative" style={{ aspectRatio: "1440 / 555" }} >
      <nav className="container flex justify-between items-center relative z-30 h-20">
        <button
          className="rounded-full w-10 h-10 bg-app-surface flex justify-center items-center"
          onClick={goBack}
        >
          <ArrowBackIcon />
        </button>
        <button
          className="rounded-full w-10 h-10 bg-app-surface flex justify-center items-center"
          onClick={handleFavoriteClick}
        >
          <StarIcon isActive={isFavorite} />
        </button>
      </nav>
      <div className="container m-auto max-w-3xl top-64 relative z-30 text-white mt-20">
        <div className="launch-date">{launchDate}</div>
        <h1 className="text-4xl font-bold text-white uppercase">
          {launchName}
        </h1>
        <div className="text-2xl max-w-3xl overflow-hidden text-ellipsis whitespace-nowrap max-h-12">
          {launchDescription}
        </div>
      </div>
      <div className="absolute w-full h-full bg-detail-gradient z-20 top-0"></div>
      <div
        className="launch-image absolute w-full h-full bg-cover bg-center z-10 top-0"
    style={{ backgroundImage: `url({{launchImage}})` }}
      ></div>
    </header>
  );

Learnings and Takeaways:


This project reinforced the power of Next.js and Tailwind CSS in building modern web applications. The app router of Nextjs is very powerful, and easy to use. I also gained valuable experience in API integration, state management, and component-driven development. I learned that having a well defined type system using typescript is very important to avoid bugs. I also learned to use tailwind efficiently, and how fast I can create a UI that looks great. I'm excited to apply these learnings to future projects.

Feel free to explore the project on spacex-launches-webapp.
I deployed the app on Vercel, you can check it out here.

I'm always open to feedback and collaboration. If you're a recruiter or potential client looking for a developer with experience in Next.js, Tailwind CSS, and API integration, let's connect!

Ready to order your project?

Let's work together!
Contact me
Posted in