Next.js WeatherAPI Integration: SSR, API Routes & ISR
Next.js provides multiple rendering strategies that make it perfect for building weather applications with optimal performance. This tutorial covers implementing WeatherAPI with server-side rendering (SSR), API route proxying, and Incremental Static Regeneration (ISR) for efficient caching.
Setting Up WeatherAPI in Next.js
First, install the necessary dependencies and configure your environment:
npm install axios
# Create .env.local
WEATHER_API_KEY=your_api_key_here
NEXT_PUBLIC_BASE_URL=https://api.weatherapi.com/v1
Create a utility file for API calls:
// lib/weather.js
import axios from 'axios';
const BASE_URL = 'https://api.weatherapi.com/v1';
const API_KEY = process.env.WEATHER_API_KEY;
export const weatherAPI = axios.create({
baseURL: BASE_URL,
params: {
key: API_KEY,
},
});
export async function getCurrentWeather(location) {
const response = await weatherAPI.get('/current.json', {
params: { q: location, aqi: 'yes' }
});
return response.data;
}
export async function getForecast(location, days = 3) {
const response = await weatherAPI.get('/forecast.json', {
params: { q: location, days, aqi: 'yes', alerts: 'yes' }
});
return response.data;
}
Server-Side Rendering with getServerSideProps
For dynamic weather data that needs to be fresh on every request, use SSR:
// pages/weather/[location].js
import { getCurrentWeather, getForecast } from '../../lib/weather';
export default function WeatherPage({ current, forecast, location }) {
return (
<div>
<h1>Weather in {location}</h1>
<div>
<h2>Current: {current.temp_c}°C</h2>
<p>Feels like: {current.feelslike_c}°C</p>
<p>Condition: {current.condition.text}</p>
<p>UV Index: {current.uv}</p>
<p>AQI: {current.air_quality['us-epa-index']}</p>
</div>
<div>
<h2>3-Day Forecast</h2>
{forecast.forecastday.map((day) => (
<div key={day.date}>
<h3>{day.date}</h3>
<p>High: {day.day.maxtemp_c}°C</p>
<p>Low: {day.day.mintemp_c}°C</p>
<p>{day.day.condition.text}</p>
</div>
))}
</div>
</div>
);
}
export async function getServerSideProps({ params }) {
const location = params.location;
try {
const [currentData, forecastData] = await Promise.all([
getCurrentWeather(location),
getForecast(location, 3)
]);
return {
props: {
current: currentData.current,
forecast: forecastData.forecast,
location: currentData.location.name,
},
};
} catch (error) {
return {
notFound: true,
};
}
}
API Route Proxying
Create API routes to proxy WeatherAPI requests, adding rate limiting and error handling:
// pages/api/weather/current.js
import { getCurrentWeather } from '../../../lib/weather';
export default async function handler(req, res) {
if (req.method !== 'GET') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { q: location } = req.query;
if (!location) {
return res.status(400).json({ error: 'Location parameter required' });
}
try {
const data = await getCurrentWeather(location);
// Cache for 10 minutes
res.setHeader('Cache-Control', 'public, s-maxage=600, stale-while-revalidate=300');
res.status(200).json(data);
} catch (error) {
console.error('Weather API Error:', error.response?.data || error.message);
res.status(error.response?.status || 500).json({
error: 'Failed to fetch weather data'
});
}
}
Create a similar route for search functionality:
// pages/api/weather/search.js
import { weatherAPI } from '../../../lib/weather';
export default async function handler(req, res) {
const { q: query } = req.query;
if (!query || query.length < 3) {
return res.status(400).json({ error: 'Query must be at least 3 characters' });
}
try {
const response = await weatherAPI.get('/search.json', {
params: { q: query }
});
res.setHeader('Cache-Control', 'public, s-maxage=3600');
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error: 'Search failed' });
}
}
Incremental Static Regeneration (ISR)
For weather data that can be cached but needs periodic updates, use ISR:
// pages/forecast/[location].js
import { getForecast } from '../../lib/weather';
export default function ForecastPage({ forecast, location, lastUpdated }) {
return (
<div>
<h1>14-Day Forecast for {location}</h1>
<p>Last updated: {new Date(lastUpdated).toLocaleString()}</p>
<div>
{forecast.forecastday.map((day) => (
<div key={day.date}>
<h2>{day.date}</h2>
<div>
<strong>{day.day.maxtemp_c}°C / {day.day.mintemp_c}°C</strong>
<p>{day.day.condition.text}</p>
<p>Chance of rain: {day.day.daily_chance_of_rain}%</p>
</div>
<div>
<h3>Hourly</h3>
{day.hour.filter((_, i) => i % 3 === 0).map((hour) => (
<span key={hour.time}>
{new Date(hour.time).getHours()}:00 - {hour.temp_c}°C
</span>
))}
</div>
</div>
))}
</div>
</div>
);
}
export async function getStaticProps({ params }) {
try {
const data = await getForecast(params.location, 14);
return {
props: {
forecast: data.forecast,
location: data.location.name,
lastUpdated: new Date().toISOString(),
},
revalidate: 1800, // Revalidate every 30 minutes
};
} catch (error) {
return {
notFound: true,
revalidate: 60, // Retry after 1 minute on error
};
}
}
export async function getStaticPaths() {
// Pre-generate popular cities
const popularCities = ['London', 'New York', 'Tokyo', 'Sydney'];
return {
paths: popularCities.map((city) => ({
params: { location: city },
})),
fallback: 'blocking',
};
}
Client-Side Data Fetching
For interactive components, use SWR or React Query with your API routes:
// components/WeatherSearch.js
import { useState } from 'react';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
export default function WeatherSearch() {
const [query, setQuery] = useState('');
const [selectedLocation, setSelectedLocation] = useState(null);
const { data: suggestions } = useSWR(
query.length > 2 ? `/api/weather/search?q=${query}` : null,
fetcher,
{ dedupingInterval: 300000 }
);
const { data: weather, error } = useSWR(
selectedLocation ? `/api/weather/current?q=${selectedLocation}` : null,
fetcher,
{ refreshInterval: 600000 }
);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search for a city..."
/>
{suggestions && (
<ul>
{suggestions.map((location) => (
<li
key={location.id}
onClick={() => setSelectedLocation(location.name)}
>
{location.name}, {location.country}
</li>
))}
</ul>
)}
{weather && (
<div>
<h2>{weather.location.name}</h2>
<p>{weather.current.temp_c}°C</p>
</div>
)}
</div>
);
}
Performance Optimization
WeatherAPI’s fast response times (~200ms average) work perfectly with Next.js caching strategies. Use ISR for forecast pages that don’t need real-time updates, SSR for critical current conditions, and client-side fetching for interactive search features.
Ready to build weather applications with Next.js? Sign up for WeatherAPI and get 100,000 free API calls per month with no credit card required.
