React  

Building a Weather App with Next.js and Tailwind

A lot of tutorials show you how to build apps just for the sake of it. I wanted to create something practical and clean, something I’d use. So I built a weather app using Next.js, Tailwind CSS, and the OpenWeather API. The goal: keep the experience simple, fast, and mobile-friendly.

In this post, I’ll walk you through how I built the app, the technology I used, and why. You’ll also get the full code and design logic behind each part.

Why This Project?

Checking the weather should be fast. Most apps and sites overload you with data, ads, or features you don’t need. I wanted:

  • A minimal UI
  • Instant search by city
  • Clean display of current weather
  • A 5-day forecast that’s easy to skim
  • No unnecessary clicks or clutter

And I wanted to build it using modern web tools that I enjoy working with.

Tech Stack Breakdown

Next.js (App Router)

I used Next.js because it provides:

  • File-based routing
  • Built-in API routes (to hide API keys)
  • Client/server flexibility
  • Great dev experience

Tailwind CSS

Tailwind gave me:

  • Fast styling using utility classes
  • Design consistency
  • Full control over responsiveness and layout

OpenWeather API

For weather data, I used OpenWeather, which offers:

  • A free tier for development
  • Easy endpoints for current weather and forecasts
  • Icons and readable weather data

Environment Variables

To keep the API key safe, I stored it in .env.local and used Next.js server routes to fetch the data, so the key never touches the frontend.

App Layout

I kept the layout consistent across all pages using app/layout.js the Next.js App Router.

  • Header.js:Shows the app name and tagline
  • Footer.js: Credits and copyright
  • layout.js: Wraps everything and sets up fonts, spacing, and flex layout

This keeps the structure predictable and avoids repetition on every page.

The Search Component

The search bar (SearchBar.js) is where users enter a city. It’s a controlled input, styled with Tailwind and enhanced with an SVG icon for clarity.

Key features:

  • Prevents empty searches
  • Triggers the weather and forecast fetch
  • Styled for responsiveness and accessibility
const [city, setCity] = useState("");

const handleSubmit = (e) => {
  e.preventDefault();
  if (city.trim()) onSearch(city);
};

Fetching Weather Data

Why not fetch directly from the client?

Because that would expose your API key in the browser, which is a bad idea, I used Next.js API routes to call the OpenWeather API securely instead.

API Utility Functions

Inside lib/weather.jsI created fetchers for weather and forecast data:

export async function getWeather(city) {
  const res = await fetch(`/api/weather?city=${encodeURIComponent(city)}`);
  if (!res.ok) throw new Error("City not found");
  return res.json();
}

Server Routes (API Proxies)

These routes live in app/api/weather/route.js and app/api/forecast/route.js.

They:

  • Accept the city query parameter
  • Validate the input
  • Use process.env.OPENWEATHER_KEY
  • Return JSON to the frontend

Example:

export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const city = searchParams.get("city");
  if (!city) {
    return new Response(JSON.stringify({ error: "City is required" }), { status: 400 });
  }

  const apiKey = process.env.OPENWEATHER_KEY;
  const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`;
  const res = await fetch(url);
  const data = await res.json();

  if (!res.ok) {
    return new Response(JSON.stringify({ error: data.message }), { status: res.status });
  }

  return new Response(JSON.stringify(data), { status: 200 });
}

Displaying Current Weather

The WeatherCard.js Component displays:

  • City name
  • Current condition (e.g., "Clear")
  • Weather icon
  • Temperature, wind, and humidity

Each piece of data is pulled from the API response and styled using Tailwind's gradients and shadows.

Showing the 5-Day Forecast

The OpenWeather forecast API returns data in 3-hour intervals, so to show just one summary per day, I filtered it like this:

const days = forecast.list.filter((_, i) => i % 8 === 0);

Each forecast card shows:

  • Date
  • Weather icon
  • Temperature
  • Condition (e.g., "Rain")

Everything's laid out in a responsive grid inside ForecastCard.js.

Main Page Logic (page.js)

This file ties it all together. It:

  • Handles state for weather, forecast, and errors
  • Calls API functions when the user searches
  • Displays different content depending on what's loaded
const [weather, setWeather] = useState(null);
const [forecast, setForecast] = useState(null);
const [error, setError] = useState("");

const search = async (city) => {
  try {
    setError("");
    const current = await getWeather(city);
    const forecastData = await getForecast(city);
    setWeather(current);
    setForecast(forecastData);
  } catch (err) {
    setError("City not found");
    setWeather(null);
    setForecast(null);
  }
};

The component renders:

  • A placeholder when nothing is searched
  • An error message is returned if the API call fails
  • The WeatherCard and ForecastCard If data is returned

Environment Variable Setup

In ".env.local" I stored the API key like this:

OPENWEATHER_KEY=your_openweather_api_key

This file should be in .gitignore and never committed to version control.

What the App Can Do

  • Search any city in the world
  • Get the current weather and forecast
  • Securely call APIs via server routes
  • Fully responsive layout
  • Clean and modern design with gradients and icons

What I’d Add Next

If I keep working on it, here’s what I might add:

  • Geolocation support (get your weather automatically)
  • Save favorite cities to localStorage
  • Dark mode toggle
  • Temperature charts (using Recharts or Chart.js)

Final Thoughts

This app was simple to build but incredibly satisfying. It reinforced key frontend concepts (state, conditional rendering), backend concepts (API proxies, env vars), and design thinking (mobile-first, UX clarity).

If you’re learning React, Next.js, or just want a project that’s both useful and teachable, this is a great one to build.

Preview

Preview

Links

This article focuses on the key parts of the project, the files that handle logic, data fetching, and UI rendering. There are several other supporting files in the codebase (fonts, global styles, layout wrappers, etc.) that aren’t covered here but are included in the repo.

Note. Download the code from the top of the article.