Next.js getServerSideProps
Introduction
In modern web development, efficiently fetching and rendering data is crucial for creating performant applications. Next.js, a popular React framework, provides several methods for data fetching, with getServerSideProps
being one of the most powerful options for server-side rendering (SSR).
getServerSideProps
is a Next.js function that allows you to fetch data on the server for every request. This means the page will be generated at request time, making it perfect for pages that need access to request-specific information or require always-fresh data.
Understanding Server-Side Rendering
Before diving into getServerSideProps
, let's understand why server-side rendering matters:
- SEO Benefits: Content is rendered on the server, making it visible to search engines
- Improved Initial Load Experience: Users see fully rendered content immediately, not a loading state
- Access to Server-Side Environment: Direct access to databases, APIs with private keys, etc.
- Request-Specific Data: Access to cookies, headers, and other request information
Basic Usage of getServerSideProps
getServerSideProps
is an async function exported from a page that Next.js will call at request time. It receives a context object containing request specific parameters.
Here's a basic example:
// pages/profile.js
export default function ProfilePage({ user }) {
return (
<div>
<h1>User Profile</h1>
<p>Username: {user.username}</p>
<p>Email: {user.email}</p>
</div>
)
}
export async function getServerSideProps() {
// Fetch data from an API
const res = await fetch('https://api.example.com/user')
const user = await res.json()
// Pass data to the page via props
return {
props: { user }
}
}
When a request is made to this page:
- Next.js server receives the request
getServerSideProps
runs and fetches the data- The page is rendered with the fetched data
- The HTML is sent to the client along with minimal JavaScript needed
- The page becomes interactive once JavaScript loads (hydration)
The Context Parameter
getServerSideProps
receives a single parameter called context
which contains request-specific information:
export async function getServerSideProps(context) {
const {
params, // Route parameters
req, // HTTP request object
res, // HTTP response object
query, // Query string parameters
preview, // Preview mode if enabled
previewData, // Preview data if available
resolvedUrl, // URL of the current page
locale, // Active locale if i18n is enabled
locales, // Configured locales if i18n is enabled
defaultLocale // Default locale if i18n is enabled
} = context;
// ...
}
Working with Dynamic Routes
One of the most common use cases for getServerSideProps
is fetching data for dynamic routes:
// pages/posts/[id].js
export default function Post({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
)
}
export async function getServerSideProps(context) {
const { params } = context
const res = await fetch(`https://api.example.com/posts/${params.id}`)
const post = await res.json()
return {
props: { post }
}
}
In this example, when a user visits /posts/123
, the params.id
will be "123"
, allowing you to fetch that specific post.
Using Request and Response Objects
Since getServerSideProps
runs on the server, you have access to the Node.js request and response objects:
// pages/dashboard.js
export default function Dashboard({ user }) {
return (
<div>
<h1>Welcome, {user ? user.name : 'Guest'}</h1>
{/* Dashboard content */}
</div>
)
}
export async function getServerSideProps({ req, res }) {
// Get the user's session token from cookies
const token = req.cookies.token
// If no token exists, redirect to login
if (!token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
// Verify the token and get user data
try {
const user = await verifyUserToken(token)
return {
props: { user },
}
} catch (error) {
// If token is invalid, clear the cookie and redirect
res.setHeader('Set-Cookie', 'token=; Max-Age=0; Path=/');
return {
redirect: {
destination: '/login',
permanent: false,
},
}
}
}
Return Values from getServerSideProps
getServerSideProps
can return several different objects:
1. Return Props
The most common return value is an object with a props
key:
export async function getServerSideProps() {
return {
props: {
message: "Hello from the server!",
timestamp: new Date().toISOString(),
}
}
}
2. Return a Redirect
You can redirect users to another page:
export async function getServerSideProps() {
const userLoggedIn = false;
if (!userLoggedIn) {
return {
redirect: {
destination: '/login',
permanent: false,
}
}
}
return {
props: {}
}
}
3. Return NotFound
You can return a 404 page:
export async function getServerSideProps() {
const data = await fetchSomeData();
if (!data) {
return {
notFound: true,
}
}
return {
props: { data }
}
}
Error Handling
Proper error handling in getServerSideProps
is important:
// pages/products/[id].js
export default function Product({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
)
}
export async function getServerSideProps(context) {
try {
const { id } = context.params;
const res = await fetch(`https://api.example.com/products/${id}`);
// Check if the request was successful
if (!res.ok) {
// If product not found, return 404
if (res.status === 404) {
return { notFound: true }
}
// For other errors, throw to be caught by catch block
throw new Error(`Failed to fetch product: ${res.status}`)
}
const product = await res.json();
return {
props: { product }
}
} catch (error) {
console.error('Error in getServerSideProps:', error);
// You can handle different errors differently
// For example, return some props indicating an error
return {
props: {
error: 'Failed to load product data',
product: { name: 'Error', description: 'Could not load product', price: 0 }
}
}
// Or redirect to an error page
// return {
// redirect: {
// destination: '/error',
// permanent: false,
// }
// }
}
}
Real-World Example: Building a Weather App
Let's create a practical example of a weather app that uses getServerSideProps
to fetch weather data based on the user's location:
// pages/weather.js
import { useState } from 'react';
export default function WeatherPage({ initialWeather, locationName }) {
const [weather, setWeather] = useState(initialWeather);
return (
<div className="weather-container">
<h1>Weather for {locationName}</h1>
<div className="weather-card">
<div className="temperature">
<span className="value">{weather.temperature}</span>
<span className="unit">°C</span>
</div>
<div className="conditions">
<p>{weather.conditions}</p>
<p>Humidity: {weather.humidity}%</p>
<p>Wind: {weather.windSpeed} km/h</p>
</div>
</div>
{/* Additional UI elements like forecast, etc. */}
</div>
);
}
export async function getServerSideProps({ req, query }) {
// Get location from query parameters or use IP geolocation as fallback
let { lat, lon } = query;
let locationName = 'your location';
// If coordinates aren't provided in the URL, try to get them from IP
if (!lat || !lon) {
try {
// Get client IP (in production, you'd use a more reliable method)
const ip = req.headers['x-forwarded-for'] || '8.8.8.8'; // Default to Google DNS IP
// Use a geolocation API to get coordinates from IP
const geoRes = await fetch(`https://ipapi.co/${ip}/json/`);
const geoData = await geoRes.json();
lat = geoData.latitude;
lon = geoData.longitude;
locationName = `${geoData.city}, ${geoData.country_name}`;
} catch (error) {
console.error('Error getting location from IP:', error);
// Default coordinates for New York if geolocation fails
lat = 40.7128;
lon = -74.0060;
locationName = 'New York, USA';
}
}
// Fetch weather data using coordinates
try {
const apiKey = process.env.WEATHER_API_KEY; // Stored securely in environment variables
const weatherRes = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${lat},${lon}`
);
if (!weatherRes.ok) {
throw new Error(`Weather API responded with status ${weatherRes.status}`);
}
const weatherData = await weatherRes.json();
// Format the data for our component
const initialWeather = {
temperature: weatherData.current.temp_c,
conditions: weatherData.current.condition.text,
humidity: weatherData.current.humidity,
windSpeed: weatherData.current.wind_kph,
icon: weatherData.current.condition.icon,
};
return {
props: {
initialWeather,
locationName,
}
};
} catch (error) {
console.error('Error fetching weather data:', error);
// Return placeholder data if the API call fails
return {
props: {
initialWeather: {
temperature: '--',
conditions: 'Weather data unavailable',
humidity: '--',
windSpeed: '--',
icon: '',
},
locationName,
}
};
}
}
This example demonstrates several important concepts:
- Using query parameters for data fetching
- Accessing request headers for IP-based geolocation
- Fetching from multiple APIs (geolocation and weather)
- Error handling and providing fallbacks
- Keeping API keys secure on the server
- Formatting API responses for component consumption
Performance Considerations
While getServerSideProps
is powerful, it comes with performance implications:
- Server Load: Every page request runs the function, potentially causing high server load
- TTFB (Time To First Byte): Users must wait for the data fetching to complete before receiving any HTML
- Caching: Pages using
getServerSideProps
cannot be cached by CDNs automatically
For pages that don't need fresh data on every request, consider alternatives like:
getStaticProps
- For data that can be fetched at build timegetStaticProps
withrevalidate
- For Incremental Static Regeneration (ISR)- Client-side data fetching - For non-critical or user-specific data
When to Use getServerSideProps
getServerSideProps
is ideal when:
- You need access to the request object (cookies, headers)
- You need to show data that changes frequently (real-time data)
- The page needs to adapt to the user context on every request
- You need to access server-side resources directly (databases, file systems)
When Not to Use getServerSideProps
Avoid getServerSideProps
when:
- The data rarely changes (use
getStaticProps
instead) - You're fetching user-specific, non-SEO data that isn't needed for the initial render
- The page could benefit from CDN caching
- You want the fastest possible TTFB (Time to First Byte)
Summary
getServerSideProps
is a powerful Next.js feature that enables server-side rendering with fresh data on every request. It gives you access to request-specific information and lets you securely interact with external services before sending HTML to the client.
Key takeaways:
- Use it for pages that need fresh data on every request
- Access request information through the
context
parameter - Return props, redirects, or 404s based on your needs
- Implement proper error handling
- Consider performance implications for high-traffic pages
By understanding when and how to use getServerSideProps
, you can create dynamic, SEO-friendly pages that provide excellent user experiences while maintaining the security benefits of server-side operations.
Additional Resources
- Next.js Documentation on getServerSideProps
- Data Fetching in Next.js
- Understanding Server-Side Rendering in React
Exercises
-
Basic Implementation: Create a page that displays the current time using
getServerSideProps
. -
Dynamic Routes: Build a blog post page that fetches post content based on the URL slug.
-
Authentication Flow: Implement a protected page that redirects unauthenticated users using cookies and the redirect feature.
-
Error Handling: Create a product page that properly handles API errors and displays appropriate messages.
-
Advanced Challenge: Build a dashboard that fetches data from multiple APIs, handles errors gracefully, and formats the data for display.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)