In this Next.js tutorial, we'll learn how to create a simple weather widget using Next.js, React, and OpenWeatherMap.
I'll be keeping things simple, so styling will be done using plain old CSS, and the weather API calls will use the built-in fetch API.
Note: You can download the final code for this tutorial from my GitHub repo.
Why use Next.js?
This widget is easily doable using basic React, and for the most part, the code will be just basic React. However, we'll need a way to allow backend-only access to our OpenWeatherMap API key (for security purposes) and make the API calls, and Next.js provides us with an integrated way to do this by utilizing the pages/api
folder.
Of course, alternatively, if you were using Node.js or another backend (Java, PHP, Python, Ruby, etc.), you would just place all of your API calls there instead of using the backend we'll be creating. That however is outside the scope of this tutorial, and so we'll be sticking with a full Next JS build for this one.
Alright, let's get started!
Bootstrap the Project (optional)
If you're using an existing project, then you've already bootstrapped your project, and can skip ahead to the Setup your OpenWeatherMap API Key section below.
Otherwise, the first step will be to set up the project boilerplate.
1. Create the Project
From the CLI, using yarn, run the following command:
yarn create next-app weather-widget --example with-typescript
Or, if you're using npx, you can use the following:
npx create-next-app weather-widget --example with-typescript
Then, when prompted, use the following settings:
✔ Would you like to use TypeScript? … [Yes]
✔ Would you like to use ESLint? … [Yes]
✔ Would you like to use Tailwind CSS? … [No]
✔ Would you like to use `src/` directory? … [No]
✔ Would you like to use App Router? (recommended) … [No]
✔ Would you like to customize the default import alias (@/*)? … [No]
Note: If you're using Yarn's PnP (no node_modules folder) along with VS Code as your IDE and are seeing "Cannot find module ..." errors in your TS files after the install, see my post here on how to fix those errors.
2. Delete Unnecessary Files
Delete the following files (but not the folders):
public/*
styles/*
pages/api/*
README.md
3. Delete Unnecessary Code
In pages/_app.tsx
, delete the following import:
// pages/_app.tsx
import '../styles/globals.css';
Setup your OpenWeatherMap API Key
OpenWeatherMap offers a free API tier, which allows for up to 60 calls per minute, and max 1,000,000 calls per month. So we've got plenty for the purposes of this tutorial.
To get your free key, go to openweathermap.org and create an account.
Once you've created your account:
- Go to the API Keys page and copy your API key.
- Create a new file in your weather widget project root folder called
.env.local
and add the following code, replacing[YOUR_API_KEY_HERE]
with the API key you obtained from OpenWeatherMap:
# .env.local
OPENWEATHERMAP_API_KEY=[YOUR_API_KEY_HERE]
Create the Weather Data API
The weather API is where we need Next.js to come in and help us out.
We'll be creating a basic API that will allow us to make calls to OpenWeatherMap from the frontend, without exposing our API key, by routing those calls through our backend (.js
or .ts
files in the pages/api
folder).
In the pages/api
folder, create a file called weather.ts
, and add the following code:
// pages/api/weather.ts
// import the Next.js request and response types
import { NextApiRequest, NextApiResponse } from 'next';
// import the OpenWeatherMap API key from the .env.local file
const apiKey = process.env.OPENWEATHERMAP_API_KEY;
const apiUrl = 'https://api.openweathermap.org/data/2.5/weather';
export default async (req: NextApiRequest, res: NextApiResponse) => {
// extract the query parameters from the request object
const { q, lat, lon } = req.query;
// if the API key isn't found in the .env.local file,
// return a 500 error to the frontend
if (!apiKey) {
res.status(500).json({ error: 'API key not found.' });
return;
}
// if either the city name (q), or coordinates (lat and lon) aren't
// provided, return a 400 error to the frontend
if (!q && (!lat || !lon)) {
res.status(400).json({ error: 'Please provide either city or coordinates.' });
return;
}
// because this is running server-side, we're wrapping the API call
// in a try / catch block to catch any errors that may occur,
// and if so, return a 500 error to the frontend
try {
const query = q ? `q=${q}` : `lat=${lat}&lon=${lon}`;
const response = await fetch(`${apiUrl}?${query}&appid=${apiKey}&units=metric`);
const data = await response.json();
res.status(200).json(data);
} catch (error: unknown) {
if (error instanceof Error) {
res.status(500).json({ error: error.message });
} else {
res
.status(500)
.json({
error: error || 'Server error while trying to fetch weather data',
});
}
}
};
Just like Node.js, Next.js gives us access to the process.env
object, which allows us to access environment variables, like our OPENWEATHERMAP_API_KEY
constant.
After a few basic checks (check that the query
parameter was passed with either city name q
or coords lat
and lon
), we then use the fetch
function to make the API call to OpenWeatherMap, and return the data to the frontend via the response method.
If the call was successful, we return the data to the frontend with an HTTP 200 status code, otherwise we return an error message with the generic HTTP server error code 500.
Define the Widget Styling
Next up, we'll define the styling for our widget.
In a project this small, we could just use a regular CSS file, which would make the CSS classes global.
I want to scope the CSS to the widget only though, which will avoid possible naming conflicts with other CSS files that could occur in larger projects.
So in the styles
folder, create a CSS module file called weather.module.css
, and add the following CSS:
/* styles/weather.module.css */
.weatherWidget {
border: 1px solid #ccc;
border-radius: 10px;
max-width: 200px;
padding: 20px;
margin: 10px;
text-align: center;
background-color: #ccc;
}
.currentWeather {
margin: 0 auto;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
.currentWeather div {
display: inline-block;
font-size: 2.5rem;
}
.feelsLike {
font-size: 0.9rem;
font-style: italic;
}
.weather {
font-weight: bold;
}
Create the Weather Widget Component
Now it's time to create the actual widget component.
In your project root folder, create a new folder called components
, and then inside that folder, either using bash or your code editor, create a file called weatherWidget.tsx
.
In weatherWidget.tsx
, add the following code:
// components/weatherWidget.tsx
import React, { useState, useEffect } from 'react';
import styles from '../styles/widget.module.css';
interface WeatherWidgetProps {
city?: string;
coordinates?: { lat: number; lon: number };
}
interface WeatherData {
name: string;
main: {
temp: number;
feels_like: number;
};
weather: {
description: "string;"
icon: string;
}[];
}
const WeatherWidget: React.FC<WeatherWidgetProps> = ({ city, coordinates }) => {
const [weatherData, setWeatherData] = useState<WeatherData | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
let query = '';
if (city) {
query = `q=${city}`;
} else if (coordinates) {
query = `lat=${coordinates.lat}&lon=${coordinates.lon}`;
} else {
console.error('Please provide either city or coordinates.');
return;
}
const response = await fetch(`/api/weather?${query}`);
const data: WeatherData = await response.json();
setWeatherData(data);
} catch (error) {
console.error('Error fetching weather data:', error);
}
};
fetchData();
}, [city, coordinates]);
return (
<div className={styles.weatherWidget}>
{!weatherData ? (
<div>Loading weather ...</div>
) : (
<>
<h2>{weatherData.name}</h2>
<p className={styles.weather}>{weatherData.weather[0].description}</p>
<div className={styles.currentWeather}>
<img
src={`https://openweathermap.org/img/wn/${weatherData.weather[0].icon}@2x.png`}
alt={weatherData.weather[0].description}
/>
<div>{Math.round(weatherData.main.temp)}°C</div>
</div>
<p className={styles.feelsLike}>
Feels like: {Math.round(weatherData.main.feels_like)}°C
</p>
</>
)}
</div>
);
};
export default WeatherWidget;
In the above code, we're using the useState
and useEffect
hooks to fetch the weather data from our API, and then display it in the widget.
We're using useEffect
so that the API call can be made if / when the city
or coordinates
props change. So if this was in an app with a select box of cities, the widget would update automatically when the user selects a new city.
Note: I've left out proper error handling for the weather API call, opting to just log the error to the console for the sake of this tutorial.
In a real app, you'd want to handle any errors more gracefully, perhaps by displaying a message to the user, and probably by using a state variable to track the error, and then displaying the error message in the widget.
Update the Index Page
Finally, we'll update the index.tsx
page to use our new widget.
Simply replace the contents of the original boilerplate in pages/index.tsx
with the following code:
// pages/index.tsx
import React from 'react';
import WeatherWidget from '../components/weatherWidget';
const Home: React.FC = () => {
return (
<div className="App">
{/* Example using city name */}
<WeatherWidget city="Montreal" />
{/* Example using coordinates */}
{/* <WeatherWidget coordinates={{ lon: -73.5878, lat: 45.5088 }} /> */}
</div>
);
};
export default Home;
In the above code, I'm using "Montreal" as the city name, but you can of course replace that with any city name you want.
If you have trouble getting the weather for a particular city, you can comment out that line, and uncomment the second example which uses the coordinates for that city instead.
And that's it for the setup and tutorial!
The Final Result
Now, if you run yarn dev
(or npm run dev
), you should get something like the following widget showing in your browser:
Additional Thoughts
The OpenWeatherMap API is pretty robust, and there are other paid API endpoints that you can access, such as 4-day, 8-day, and 16-day forecasts, among other things.
Because I'm using the free version of the API in this post however, the access is limited to current weather and a 5-day forecast.
For further info on the free "current weather" API we're using in this tutorial, refer to the Current weather data API documentation for all the available data points.
Top comments (0)