Overview
Weather Now is a real-time weather application built as part of a Frontend Mentor hackathon. Users can search for any city, let the app detect their location via the browser geolocation API, and view current conditions plus hourly and daily forecasts. All data comes from the free, keyless Open-Meteo APIs.
Key capabilities:
- Search by city name (debounced) with a country-flag results dropdown.
- Browser geolocation for "current location" weather on first load.
- Current conditions widget: temperature, feels-like, humidity, wind, precipitation.
- 7-day daily forecast and 24-hour hourly forecast.
- Metric/imperial unit switching (Celsius/Fahrenheit, km/h/mph, mm/in) from a Units dropdown.
- Fully responsive dark UI styled with CSS Modules.
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19, TypeScript |
| Client state | Zustand |
| Server state | TanStack Query (React Query) |
| Styling | CSS Modules + design tokens in globals.css |
| Weather data | Open-Meteo (via the openmeteo SDK) |
| Geocoding | Open-Meteo Geocoding API |
| Reverse geocode + timezone | BigDataCloud (needs REVERSE_GEOCODING_WITH_TIMEZONE_API env var) |
| Fonts | Local DM Sans + Bricolage Grotesque (next/font/local) |
| Deploy | Vercel |
Scripts: npm run dev, npm run build, npm run start (port 3030), npm run lint.
Project Structure
weather-now/
├── app/
│ ├── layout.tsx # Root layout: fonts, metadata, globals.css
│ ├── page.tsx # Client page composing Nav, Header, Search, ForecastContainer
│ ├── not-found.tsx # 404 → Not-Found component
│ ├── fonts.ts # Local font registration (DM Sans, Bricolage)
│ └── globals.css # CSS custom properties / design tokens
│
├── components/
│ ├── MainContainer/ # Layout shell (<main>)
│ ├── Nav/ # Logo + Units dropdown trigger
│ ├── Header/ # Page heading
│ ├── Search/ # Debounced city search + results dropdown
│ ├── SearchResult/ # One result row; sets active location on click
│ ├── ForecastContainer/ # Orchestrates fetching + the three forecast sections
│ ├── CurrentWeatherContainer/ + CurrentWidget/ + SmallWidget/
│ ├── DailyForecastContainer/ + DailyForecastDay/
│ ├── HourlyForecastContainer/ + OneHourForecast.tsx/ + SelectDay/
│ ├── DropdownButton/ + UnitsContainer/ + UnitOption/ + SwitchButton/
│ └── Not-Found/
│
├── services/ # API layer
│ ├── fetchWeatherData.ts # Open-Meteo forecast (openmeteo SDK)
│ ├── citySearchService.ts # Open-Meteo geocoding search
│ ├── reverseGeocodingWithTimezone.ts # BigDataCloud (server action)
│ └── geolocationApi.ts # navigator.geolocation wrapper
│
├── store/ # Zustand stores
│ ├── userActiveLocation.store.ts
│ ├── preferences.store.ts
│ └── ui.store.ts
│
├── utils/
│ └── unitConverting.ts # C→F, km/h→mph, mm→in converters
│
├── public/ # Icons, weather images, fonts
├── design/ · docs/ · style-guide.md # Hackathon design assets
├── .env # REVERSE_GEOCODING_WITH_TIMEZONE_API
├── next.config.ts · tsconfig.json · eslint.config.mjs
└── package.json
Path alias @/* maps to the project root.
Code Flow
-
Bootstrap —
app/page.tsx(a"use client"page) rendersMainContainer→Nav,Header,Search, and wrapsForecastContainerin aQueryClientProvider. -
Search —
Search.tsxdebounces keystrokes (500 ms) and callsfetchLocationCoordinates(services/citySearchService.ts), which hits the Open-Meteo Geocoding API. EachSearchResultrenders the city with a flag (circle-flags CDN) and, on click, callssetActiveLocationon the location store. -
Fetch —
ForecastContainer(inside React Query) subscribes to the location store. On mount it also requests browser geolocation viagetUserLocation()(services/geolocationApi.ts), which persists coordinates tolocalStorageand updates the store. When a valid lat/lng exists,useQuerywith key["weatherData", latitude, longitude]runsfetchWeatherData(current/hourly/daily from Open-Meteo) in parallel withfetchCityAndTimezone(BigDataCloud, server action). The reverse-geocoded city/country name is written back into the location store via auseEffect. -
Render — The three forecast containers consume the fetched data:
CurrentWeatherContainershows the current temp + feels-like/humidity/wind/precipitationSmallWidgets.DailyForecastContainermaps daily max/min temps intoDailyForecastDaycards (weekday labels).HourlyForecastContainerslices the first 24 hours intoOneHourForecastrows with aSelectDaydropdown.
-
Units —
Nav→DropdownButtontoggles a sidebar (UI store) that showsUnitsContainer, which lists temperature/wind/precipitationUnitOptions plus aSwitchButtonto flip the whole system. All choices live in the preferences store; every forecast component reads those flags and converts values withutils/unitConverting.ts.
Diagrams
Sequence diagram — user flow

State Management
Zustand stores (all client-side):
userActiveLocation.store.ts—location { name, country, latitude, longitude }+setActiveLocation. Source of truth for where the app is showing weather.preferences.store.ts—isMetricplus per-measurement flags (isCelsius,isKm,isMm) andtoggle*actions.toggleMetricSystemflips all three at once.ui.store.ts—sidebarOpen+toggleSidebarto show/hide the Units panel.
TanStack Query owns all server state: the weather/reverse-geocode payload is cached by coordinate pair, giving deduplication, background refetch, and built-in loading/error states.
APIs
| API | Used for |
|---|---|
Open-Meteo Forecast API (https://api.open-meteo.com/v1/forecast) | Current conditions, hourly temp, daily min/max + weather code (via openmeteo SDK) |
| Open-Meteo Geocoding API | City name → coordinates |
| BigDataCloud reverse-geocode-with-timezone | Coordinates → city/country name (requires REVERSE_GEOCODING_WITH_TIMEZONE_API) |
| circle-flags CDN | Country flag SVGs in search results |
No API keys are required for Open-Meteo; only the BigDataCloud env var is needed.
Design Decisions
- React Query for server state, Zustand for UI state — clean separation; forecasts are cached/deduped while preferences and location stay in lightweight stores.
- Co-located CSS Modules — each component ships with its own
.module.css, scoped at build time with no runtime overhead. - Design tokens — a single
:rootblock inglobals.cssdefines the neutral/blue palette and spacing scale, keeping colors consistent across components. - Open-Meteo — free and keyless, chosen over paid weather providers for a hackathon.
- Local fonts — self-hosted woff2 via
next/font/localfor performance and offline reliability.
Current State & Known WIP
This is a hackathon snapshot, so a few pieces are unfinished:
- Weather icons are hardcoded (
icon-drizzle.webp,icon-fog.webp) — there are TODOs to map Open-Meteo weather codes to the availablepublic/images/icon-*.webpset. SelectDayin the hourly section is a styled static<select>not yet wired to state (TODO to make it a custom select connected to an "active day").- Error handling is partial: a reusable
Not-Found/error screen exists (used for 404), but services carry TODOs to route API failures into a global error state. - A stray
console.log(data)remains inForecastContainer. - The temperature data from Open-Meteo is returned in Celsius and converted client-side based on the preferences store.
Getting Started
npm install
# create .env with: REVERSE_GEOCODING_WITH_TIMEZONE_API=<your key>
npm run dev
Open http://localhost:3000.
Design references live in design/, docs/, and style-guide.md.
