Skip to main content

Next.js Responsive Design

In today's digital landscape, users access websites from a variety of devices with different screen sizes. Creating a responsive design ensures your Next.js application looks and functions well across all devices, from mobile phones to large desktop monitors.

Introduction to Responsive Design in Next.js

Responsive design is an approach to web development that makes your website adapt to different screen sizes and viewport conditions. Next.js, as a React framework, provides several ways to implement responsive design patterns effectively.

In this guide, we'll explore:

  • Media queries with CSS and CSS modules
  • Component-based responsive design
  • CSS-in-JS solutions for responsiveness
  • Next.js Image component for responsive images
  • Best practices for responsive layouts

Basic Responsive Design with CSS Media Queries

The most fundamental way to implement responsive design is through CSS media queries.

Using Regular CSS Files

Create a global CSS file (e.g., styles/globals.css) and import it in your _app.js:

css
/* styles/globals.css */
.container {
width: 100%;
padding: 20px;
}

/* Mobile first approach */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}

/* Tablet and above */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}

/* Desktop */
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
jsx
// pages/_app.js
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp

Here's how to use it in a component:

jsx
// pages/index.js
export default function Home() {
return (
<div className="container">
<div className="grid">
<div className="card">Item 1</div>
<div className="card">Item 2</div>
<div className="card">Item 3</div>
<div className="card">Item 4</div>
<div className="card">Item 5</div>
<div className="card">Item 6</div>
</div>
</div>
)
}

This creates a responsive grid that shows:

  • 1 column on mobile devices
  • 2 columns on tablets
  • 3 columns on desktop screens

Using CSS Modules

Next.js supports CSS Modules out of the box. This allows for component-scoped CSS with the same responsive capabilities.

css
/* components/Layout.module.css */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}

.navbar {
display: flex;
flex-direction: column;
align-items: center;
}

@media (min-width: 768px) {
.navbar {
flex-direction: row;
justify-content: space-between;
}
}
jsx
// components/Layout.js
import styles from './Layout.module.css'

export default function Layout({ children }) {
return (
<div className={styles.container}>
<nav className={styles.navbar}>
<div className="logo">MyApp</div>
<ul className="nav-links">
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
<main>{children}</main>
</div>
)
}

Responsive Design with CSS-in-JS

Next.js works well with CSS-in-JS libraries like styled-components or emotion. These libraries allow you to create responsive styles directly in your JavaScript code.

Using Styled Components

First, install the package:

bash
npm install styled-components

Then set up a responsive component:

jsx
// components/ResponsiveCard.js
import styled from 'styled-components'

const Card = styled.div`
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

/* Mobile-first approach */
width: 100%;

/* Tablet */
@media (min-width: 768px) {
width: calc(50% - 20px);
}

/* Desktop */
@media (min-width: 1024px) {
width: calc(33.333% - 20px);
}
`

export default function ResponsiveCard({ children }) {
return <Card>{children}</Card>
}

To properly configure styled-components with Next.js, create a custom document:

jsx
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage

try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})

const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}

Responsive Breakpoints with Hooks

Create custom hooks to detect screen size and adapt components accordingly:

jsx
// hooks/useMediaQuery.js
import { useState, useEffect } from 'react'

export default function useMediaQuery(query) {
const [matches, setMatches] = useState(false)

useEffect(() => {
const media = window.matchMedia(query)
if (media.matches !== matches) {
setMatches(media.matches)
}

const listener = () => setMatches(media.matches)
media.addEventListener('change', listener)

return () => media.removeEventListener('change', listener)
}, [matches, query])

return matches
}

Use this hook to conditionally render different components:

jsx
// components/Navigation.js
import useMediaQuery from '../hooks/useMediaQuery'
import MobileMenu from './MobileMenu'
import DesktopMenu from './DesktopMenu'

export default function Navigation() {
const isMobile = useMediaQuery('(max-width: 768px)')

return (
<nav>
{isMobile ? <MobileMenu /> : <DesktopMenu />}
</nav>
)
}

Responsive Images with Next.js Image Component

Next.js provides an optimized Image component that handles responsive images efficiently:

jsx
// components/HeroSection.js
import Image from 'next/image'

export default function HeroSection() {
return (
<div className="hero-container">
<Image
src="/images/hero-image.jpg"
alt="Hero image"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{
width: '100%',
height: 'auto',
}}
priority
/>
</div>
)
}

The sizes attribute tells browsers what size the image will be displayed at different breakpoints, allowing them to download the most appropriate size from the automatically generated srcset.

Creating a Responsive Layout System

Let's build a comprehensive responsive layout system:

jsx
// components/Grid.js
import styled from 'styled-components'

const GridContainer = styled.div`
display: grid;
gap: ${props => props.gap || '20px'};
width: 100%;

grid-template-columns: repeat(
auto-fill,
minmax(${props => props.minItemWidth || '300px'}, 1fr)
);

@media (max-width: 768px) {
grid-template-columns: 1fr;
}
`

export default function Grid({ children, gap, minItemWidth }) {
return (
<GridContainer gap={gap} minItemWidth={minItemWidth}>
{children}
</GridContainer>
)
}

Usage:

jsx
// pages/blog.js
import Grid from '../components/Grid'
import BlogPost from '../components/BlogPost'

export default function Blog({ posts }) {
return (
<div className="container">
<h1>Blog Posts</h1>
<Grid minItemWidth="250px" gap="30px">
{posts.map(post => (
<BlogPost key={post.id} post={post} />
))}
</Grid>
</div>
)
}

Handling Viewport-Based Units

CSS viewport units (vh, vw) can be tricky on mobile devices. Here's how to handle them properly:

css
/* styles/globals.css */
:root {
--vh: 1vh;
}

.full-height {
height: 100vh; /* Fallback */
height: calc(var(--vh) * 100);
}
jsx
// pages/_app.js
import { useEffect } from 'react'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
useEffect(() => {
// Set the --vh custom property to the actual viewport height
const setVh = () => {
const vh = window.innerHeight * 0.01
document.documentElement.style.setProperty('--vh', `${vh}px`)
}

// Initial set
setVh()

// Update on resize
window.addEventListener('resize', setVh)
return () => window.removeEventListener('resize', setVh)
}, [])

return <Component {...pageProps} />
}

export default MyApp

Responsive Typography with Fluid Type

Create a fluid typography system that scales smoothly between screen sizes:

css
/* styles/globals.css */
:root {
/* Base sizes at 320px viewport */
--h1-font-size-min: 1.75rem;
--h2-font-size-min: 1.375rem;
--body-font-size-min: 1rem;

/* Sizes at 1200px viewport */
--h1-font-size-max: 3rem;
--h2-font-size-max: 2rem;
--body-font-size-max: 1.125rem;
}

h1 {
font-size: var(--h1-font-size-min);
}

h2 {
font-size: var(--h2-font-size-min);
}

body {
font-size: var(--body-font-size-min);
}

/* Fluid typography using calc */
@media (min-width: 320px) {
h1 {
font-size: calc(var(--h1-font-size-min) + (var(--h1-font-size-max) - var(--h1-font-size-min)) * ((100vw - 320px) / (1200 - 320)));
}

h2 {
font-size: calc(var(--h2-font-size-min) + (var(--h2-font-size-max) - var(--h2-font-size-min)) * ((100vw - 320px) / (1200 - 320)));
}

body {
font-size: calc(var(--body-font-size-min) + (var(--body-font-size-max) - var(--body-font-size-min)) * ((100vw - 320px) / (1200 - 320)));
}
}

/* Lock in maximum sizes */
@media (min-width: 1200px) {
h1 {
font-size: var(--h1-font-size-max);
}

h2 {
font-size: var(--h2-font-size-max);
}

body {
font-size: var(--body-font-size-max);
}
}

Real-World Example: Responsive Dashboard Layout

Let's create a responsive dashboard layout that adapts to different screen sizes:

jsx
// components/DashboardLayout.js
import { useState } from 'react'
import styled from 'styled-components'
import useMediaQuery from '../hooks/useMediaQuery'

const LayoutContainer = styled.div`
display: flex;
flex-direction: ${props => props.isMobile ? 'column' : 'row'};
height: 100vh;
`

const Sidebar = styled.aside`
background: #f8f9fa;
width: ${props => props.isMobile ? '100%' : '250px'};
height: ${props => props.isMobile ? (props.isOpen ? '300px' : '60px') : '100vh'};
transition: height 0.3s ease;
overflow: ${props => props.isMobile && !props.isOpen ? 'hidden' : 'auto'};
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
`

const Content = styled.main`
flex: 1;
padding: 20px;
overflow: auto;
`

const MenuToggle = styled.button`
display: ${props => props.isMobile ? 'block' : 'none'};
width: 100%;
border: none;
background: #e9ecef;
padding: 15px;
text-align: left;
font-weight: bold;
`

export default function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false)
const isMobile = useMediaQuery('(max-width: 768px)')

return (
<LayoutContainer isMobile={isMobile}>
<Sidebar isMobile={isMobile} isOpen={sidebarOpen}>
<MenuToggle
isMobile={isMobile}
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{sidebarOpen ? 'Close Menu' : 'Open Menu'}
</MenuToggle>
{/* Sidebar content */}
<nav>
<ul style={{ listStyle: 'none', padding: '15px' }}>
<li style={{ margin: '10px 0' }}>Dashboard</li>
<li style={{ margin: '10px 0' }}>Analytics</li>
<li style={{ margin: '10px 0' }}>Reports</li>
<li style={{ margin: '10px 0' }}>Settings</li>
</ul>
</nav>
</Sidebar>
<Content>{children}</Content>
</LayoutContainer>
)
}

Usage example:

jsx
// pages/dashboard.js
import DashboardLayout from '../components/DashboardLayout'
import Grid from '../components/Grid'
import Card from '../components/Card'

export default function Dashboard() {
return (
<DashboardLayout>
<h1>Dashboard</h1>
<Grid minItemWidth="300px" gap="20px">
<Card>
<h2>Sales</h2>
<p>$12,345</p>
</Card>
<Card>
<h2>Orders</h2>
<p>45</p>
</Card>
<Card>
<h2>Customers</h2>
<p>189</p>
</Card>
<Card>
<h2>Revenue</h2>
<p>$8,567</p>
</Card>
</Grid>
</DashboardLayout>
)
}

Best Practices for Responsive Design in Next.js

  1. Mobile-First Approach: Start by designing for mobile devices and then enhance for larger screens. This ensures your application works well on all devices.

  2. Use Relative Units: Prefer rem, em, %, vh, vw over fixed px for measurements.

  3. Test on Real Devices: Simulators are helpful, but nothing beats testing on actual devices.

  4. Performance Considerations: Responsive doesn't just mean visually adapting. Consider loading smaller assets for mobile devices to improve performance.

  5. Use Layout Shifts Metrics: Monitor CLS (Cumulative Layout Shift) to ensure your responsive designs don't cause jarring visual changes.

  6. Responsive Typography: Ensure your text is readable on all screen sizes without requiring zoom.

  7. Touch-Friendly UI: Make sure interactive elements are large enough for touch interaction (at least 44×44 pixels).

Summary

Creating responsive designs in Next.js involves:

  • Using CSS media queries for adapting layouts to different screen sizes
  • Leveraging component-based design to create responsive UI elements
  • Using responsive images with the Next.js Image component
  • Creating fluid typography that scales appropriately
  • Implementing responsive layouts with CSS Grid and Flexbox
  • Building or using responsive navigation patterns
  • Testing across multiple devices and screen sizes

By following these principles and practices, you can create Next.js applications that provide an excellent user experience across all devices, from mobile phones to large desktop monitors.

Additional Resources

Practice Exercises

  1. Create a responsive navigation bar that transforms into a hamburger menu on mobile devices.
  2. Build a responsive image gallery that displays different numbers of columns based on screen size.
  3. Implement a responsive form that adjusts field layouts for different screen widths.
  4. Create a responsive data table that converts to cards on small screens.
  5. Build a landing page hero section with responsive typography and imagery.

By mastering responsive design in Next.js, you'll be able to create web applications that look and function beautifully on any device!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)