Skip to content

Top 15 Next.js Interview Coding Challenges with Solutions [2026]

September 8, 2026 13 min read
Next.js interview guide by Second Talent

Master Next.js development with these comprehensive coding challenges covering SSR, SSG, API routes, middleware, performance optimization, and modern React patterns. From basic routing to advanced production-ready applications.

Junior Level Challenges

1. Dynamic Routing with getServerSideProps

Difficulty: Beginner | Topics: Dynamic Routes, SSR, Data Fetching

Challenge: Create a dynamic blog post page that fetches data at request time using getServerSideProps.

// pages/blog/[slug].js
export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <time>{new Date(post.publishedAt).toLocaleDateString()}</time>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

export const getServerSideProps = async ({ params }) => {
  const { slug } = params;
  
  try {
    const response = await fetch(`${process.env.API_URL}/posts/${slug}`);
    if (!response.ok) return { notFound: true };
    
    const post = await response.json();
    return { props: { post } };
  } catch (error) {
    return { notFound: true };
  }
};

2. Static Site Generation with getStaticProps

Difficulty: Beginner | Topics: SSG, Build-time Data Fetching

Challenge: Build a products page that pre-renders at build time with cached data.

// pages/products.js
export default function Products({ products }) {
  return (
    <div className="products-grid">
      <h1>Our Products</h1>
      <div className="grid">
        {products.map((product) => (
          <div key={product.id} className="product-card">
            <img src={product.image} alt={product.name} />
            <h3>{product.name}</h3>
            <p className="price">${product.price}</p>
            <p>{product.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

export const getStaticProps = async () => {
  const response = await fetch(`${process.env.API_URL}/products`);
  const products = await response.json();
  
  return {
    props: { products },
    revalidate: 3600 // Revalidate every hour
  };
};

3. API Routes with Request Validation

Difficulty: Beginner-Intermediate | Topics: API Routes, Validation, Error Handling

Challenge: Create a user registration API endpoint with input validation and error handling.

// pages/api/auth/register.js
import bcrypt from 'bcryptjs';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' });
  }
  
  const { email, password, name } = req.body;
  
  // Validation
  if (!email || !password || !name) {
    return res.status(400).json({ message: 'All fields required' });
  }
  
  if (password.length < 8) {
    return res.status(400).json({ message: 'Password must be 8+ characters' });
  }
  
  try {
    // Check if user exists
    const existingUser = await getUserByEmail(email);
    if (existingUser) {
      return res.status(400).json({ message: 'User already exists' });
    }
    
    // Hash password and create user
    const hashedPassword = await bcrypt.hash(password, 12);
    const user = await createUser({ email, password: hashedPassword, name });
    
    res.status(201).json({
      message: 'User created successfully',
      user: { id: user.id, email: user.email, name: user.name }
    });
  } catch (error) {
    res.status(500).json({ message: 'Internal server error' });
  }
}

4. Custom App with Global State

Difficulty: Intermediate | Topics: Custom App, Context API, Global State

Challenge: Set up a custom _app.js with global authentication state and theme provider.

// pages/_app.js
import { createContext, useContext, useReducer } from 'react';
import '../styles/globals.css';

const AppContext = createContext();

const initialState = {
  user: null,
  theme: 'light',
  isLoading: false
};

function appReducer(state, action) {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload };
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload };
    default:
      return state;
  }
}

function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);
  
  return (
    <AppContext.Provider value={{ state, dispatch }}>
      <div className={`app ${state.theme}`}>
        {children}
      </div>
    </AppContext.Provider>
  );
}

export const useAppContext = () => {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useAppContext must be used within AppProvider');
  }
  return context;
};

export default function MyApp({ Component, pageProps }) {
  return (
    <AppProvider>
      <Component {...pageProps} />
    </AppProvider>
  );
}

5. Image Optimization with Next.js Image

Difficulty: Beginner | Topics: Image Optimization, Performance

Challenge: Create a responsive image gallery with optimized loading and lazy loading.

// components/ImageGallery.js
import Image from 'next/image';
import { useState } from 'react';

export default function ImageGallery({ images }) {
  const [selectedImage, setSelectedImage] = useState(null);
  
  return (
    <>
      <div className="gallery-grid">
        {images.map((image) => (
          <div
            key={image.id}
            className="gallery-item"
            onClick={() => setSelectedImage(image)}
          >
            <Image
              src={image.src}
              alt={image.alt}
              width={300}
              height={200}
              placeholder="blur"
              blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."
              style={{ objectFit: 'cover', borderRadius: '8px' }}
              sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
              priority={false}
            />
          </div>
        ))}
      </div>
      
      {selectedImage && (
        <div className="modal-overlay" onClick={() => setSelectedImage(null)}>
          <div className="modal-content">
            <Image
              src={selectedImage.src}
              alt={selectedImage.alt}
              width={selectedImage.width}
              height={selectedImage.height}
              style={{ objectFit: 'contain' }}
              priority
            />
          </div>
        </div>
      )}
    </>
  );
}

Mid-Level Challenges

6. Incremental Static Regeneration (ISR)

Difficulty: Intermediate | Topics: ISR, Caching, Performance

Challenge: Implement ISR for a news website that updates content automatically without full rebuilds.

// pages/news/[id].js
export default function NewsArticle({ article }) {
  return (
    <article className="news-article">
      <header>
        <h1>{article.title}</h1>
        <div className="article-meta">
          <span>By {article.author}</span>
          <time>{new Date(article.publishedAt).toLocaleDateString()}</time>
        </div>
      </header>
      <div dangerouslySetInnerHTML={{ __html: article.content }} />
    </article>
  );
}

export const getStaticPaths = async () => {
  // Pre-render only popular articles
  const response = await fetch(`${process.env.API_URL}/news/popular`);
  const popularArticles = await response.json();
  
  const paths = popularArticles.map((article) => ({
    params: { id: article.id }
  }));
  
  return {
    paths,
    fallback: 'blocking' // Generate other pages on-demand
  };
};

export const getStaticProps = async ({ params }) => {
  const response = await fetch(`${process.env.API_URL}/news/${params.id}`);
  if (!response.ok) return { notFound: true };
  
  const article = await response.json();
  
  return {
    props: { article },
    revalidate: 300, // Revalidate every 5 minutes
  };
};

7. Custom Document with SEO Optimization

Difficulty: Intermediate | Topics: Custom Document, SEO, Meta Tags

Challenge: Create a custom _document.js with SEO optimization and structured data.

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link rel="manifest" href="/manifest.json" />
          <meta name="theme-color" content="#000000" />
          <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
          
          {/* Structured Data */}
          <script
            type="application/ld+json"
            dangerouslySetInnerHTML={{
              __html: JSON.stringify({
                '@context': 'https://schema.org',
                '@type': 'WebSite',
                name: 'Your Site Name',
                description: 'Your site description',
                url: 'https://yoursite.com'
              })
            }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

8. Middleware for Authentication

Difficulty: Intermediate | Topics: Middleware, Authentication, Route Protection

Challenge: Implement middleware for route-based authentication and authorization.

// middleware.js
import { NextResponse } from 'next/server';
import { verify } from 'jsonwebtoken';

const protectedPaths = ['/dashboard', '/profile', '/admin'];

export function middleware(request) {
  const { pathname } = request.nextUrl;
  
  const isProtectedPath = protectedPaths.some(path => 
    pathname.startsWith(path)
  );
  
  if (!isProtectedPath) return NextResponse.next();
  
  const token = request.cookies.get('auth-token')?.value;
  
  if (!token) {
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('redirect', pathname);
    return NextResponse.redirect(loginUrl);
  }
  
  try {
    const decoded = verify(token, process.env.JWT_SECRET);
    const response = NextResponse.next();
    response.headers.set('x-user-id', decoded.userId);
    return response;
  } catch (error) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

export const config = {
  matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico).*)'],
};

9. Custom Hook for Data Fetching

Difficulty: Intermediate | Topics: Custom Hooks, SWR, Caching

Challenge: Build a reusable data fetching hook with caching, error handling, and optimistic updates.

// hooks/useApi.js
import { useState, useEffect, useCallback } from 'react';

const cache = new Map();

function useApi(url, options = {}) {
  const [data, setData] = useState(options.initialData);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const fetchData = useCallback(async (showLoading = true) => {
    if (!url) return;
    
    if (showLoading) setLoading(true);
    setError(null);
    
    try {
      // Check cache first
      if (cache.has(url)) {
        const cachedData = cache.get(url);
        setData(cachedData);
        if (showLoading) setLoading(false);
        return;
      }
      
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result = await response.json();
      cache.set(url, result);
      setData(result);
      
    } catch (err) {
      setError(err);
    } finally {
      if (showLoading) setLoading(false);
    }
  }, [url]);
  
  const mutate = useCallback(async (newData) => {
    if (newData !== undefined) {
      setData(newData);
      cache.set(url, newData);
    } else {
      await fetchData(false);
    }
  }, [url, fetchData]);
  
  useEffect(() => {
    if (url) fetchData();
  }, [url, fetchData]);
  
  return { data, error, loading, mutate, revalidate: () => fetchData(false) };
}

export default useApi;

10. Server-Side Caching with Redis

Difficulty: Expert | Topics: Server-side Caching, Redis, Performance

Challenge: Implement Redis caching for API routes with automatic invalidation.

// lib/redis.js
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

class CacheManager {
  async get(key) {
    try {
      const cached = await redis.get(key);
      return cached ? JSON.parse(cached) : null;
    } catch (error) {
      console.error('Cache get error:', error);
      return null;
    }
  }
  
  async set(key, value, ttl = 3600) {
    try {
      await redis.setex(key, ttl, JSON.stringify(value));
    } catch (error) {
      console.error('Cache set error:', error);
    }
  }
  
  async del(pattern) {
    try {
      const keys = await redis.keys(pattern);
      if (keys.length > 0) await redis.del(...keys);
    } catch (error) {
      console.error('Cache delete error:', error);
    }
  }
}

export const cacheManager = new CacheManager();

export function withCache(handler, options = {}) {
  return async (req, res) => {
    const { ttl = 3600 } = options;
    const cacheKey = `api:${req.url}`;
    
    if (req.method === 'GET') {
      const cached = await cacheManager.get(cacheKey);
      if (cached) {
        res.setHeader('X-Cache', 'HIT');
        return res.json(cached);
      }
    }
    
    const originalJson = res.json;
    res.json = function(data) {
      if (res.statusCode === 200 && req.method === 'GET') {
        cacheManager.set(cacheKey, data, ttl);
      }
      res.setHeader('X-Cache', 'MISS');
      return originalJson.call(this, data);
    };
    
    return handler(req, res);
  };
}

Senior Level Challenges

11. Real-time Updates with WebSockets

Difficulty: Expert | Topics: WebSockets, Real-time, Socket.io

Challenge: Build a real-time chat application with typing indicators and message status.

// pages/api/socketio.js
import { Server as ServerIO } from 'socket.io';

export default function handler(req, res) {
  if (!res.socket.server.io) {
    const io = new ServerIO(res.socket.server, {
      path: '/api/socketio',
    });
    
    io.on('connection', (socket) => {
      console.log('User connected:', socket.id);
      
      socket.on('join-room', (roomId) => {
        socket.join(roomId);
        socket.to(roomId).emit('user-joined', socket.id);
      });
      
      socket.on('send-message', async (data) => {
        const message = {
          id: Date.now(),
          content: data.content,
          userId: socket.userId,
          roomId: data.roomId,
          timestamp: new Date()
        };
        
        // Save to database
        await saveMessage(message);
        
        // Emit to room
        io.to(data.roomId).emit('new-message', message);
      });
      
      socket.on('typing-start', (roomId) => {
        socket.to(roomId).emit('user-typing', {
          userId: socket.userId,
          isTyping: true
        });
      });
      
      socket.on('typing-stop', (roomId) => {
        socket.to(roomId).emit('user-typing', {
          userId: socket.userId,
          isTyping: false
        });
      });
      
      socket.on('disconnect', () => {
        console.log('User disconnected:', socket.id);
      });
    });
    
    res.socket.server.io = io;
  }
  
  res.end();
}

12. Advanced Performance Optimization

Difficulty: Expert | Topics: Performance, Bundle Splitting, Lazy Loading

Challenge: Implement comprehensive performance optimizations including code splitting and resource hints.

// next.config.js
module.exports = {
  experimental: {
    optimizeCss: true,
    optimizeImages: true,
  },
  
  images: {
    domains: ['example.com'],
    formats: ['image/webp', 'image/avif'],
  },
  
  webpack: (config, { dev, isServer }) => {
    if (!dev && !isServer) {
      config.optimization.splitChunks.cacheGroups = {
        ...config.optimization.splitChunks.cacheGroups,
        vendor: {
          name: 'vendor',
          test: /node_modules/,
          chunks: 'all',
          priority: 20,
        },
      };
    }
    return config;
  },
  
  async headers() {
    return [
      {
        source: '/(.*)\\.(js|css|woff2|png|jpg)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ];
  },
};

// components/LazyComponent.js
import dynamic from 'next/dynamic';

const DynamicChart = dynamic(() => import('./Chart'), {
  loading: () => <div>Loading chart...</div>,
  ssr: false,
});

function useResourcePreload() {
  const preloadResource = (href, as) => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.href = href;
    link.as = as;
    document.head.appendChild(link);
  };
  
  return { preloadResource };
}

13. Internationalization (i18n)

Difficulty: Advanced | Topics: i18n, Localization, Dynamic Content

Challenge: Build a multi-language application with dynamic content translation.

// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'fr', 'es', 'de'],
    defaultLocale: 'en',
  },
};

// lib/i18n.js
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';

class I18nManager {
  cache = new Map();
  
  async loadNamespace(locale, namespace) {
    const cacheKey = `${locale}:${namespace}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    
    try {
      const translations = await import(`../locales/${locale}/${namespace}.json`);
      this.cache.set(cacheKey, translations.default);
      return translations.default;
    } catch (error) {
      return locale !== 'en' ? this.loadNamespace('en', namespace) : {};
    }
  }
  
  translate(translations, key, params = {}) {
    const keys = key.split('.');
    let value = translations;
    
    for (const k of keys) {
      if (value && typeof value === 'object' && k in value) {
        value = value[k];
      } else {
        return key;
      }
    }
    
    if (typeof value !== 'string') return key;
    
    return Object.entries(params).reduce(
      (str, [param, val]) => str.replace(new RegExp(`{{${param}}}`, 'g'), String(val)),
      value
    );
  }
}

export const i18nManager = new I18nManager();

export function useTranslations(namespace = 'common') {
  const { locale } = useRouter();
  const [translations, setTranslations] = useState({});
  const [isLoading, setIsLoading] = useState(true);
  
  useEffect(() => {
    const loadTranslations = async () => {
      setIsLoading(true);
      const t = await i18nManager.loadNamespace(locale, namespace);
      setTranslations(t);
      setIsLoading(false);
    };
    
    loadTranslations();
  }, [locale, namespace]);
  
  const t = (key, params) => i18nManager.translate(translations, key, params);
  
  return { t, isLoading };
}

14. Advanced Error Handling

Difficulty: Advanced | Topics: Error Boundaries, Error Tracking, Fallbacks

Challenge: Implement comprehensive error handling with custom error pages and error tracking.

// components/ErrorBoundary.js
import React, { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({ error });
    
    // Log to monitoring service
    console.error('Error caught by boundary:', error);
    
    // Call custom error handler
    this.props.onError?.(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// pages/_error.js
function ErrorPage({ statusCode }) {
  return (
    <div className="error-page">
      <h1>{statusCode ? `Server Error ${statusCode}` : 'Client Error'}</h1>
      <p>
        {statusCode === 404
          ? 'This page could not be found.'
          : 'An error occurred. Please try again later.'}
      </p>
    </div>
  );
}

ErrorPage.getInitialProps = async ({ res, err }) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  return { statusCode };
};

export default ErrorPage;

15. End-to-End Testing with Playwright

Difficulty: Advanced | Topics: E2E Testing, Test Automation, CI/CD

Challenge: Set up comprehensive end-to-end testing with Playwright including visual regression testing.

// playwright.config.js
module.exports = {
  testDir: './tests/e2e',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
  },
};

// tests/e2e/auth.spec.js
import { test, expect } from '@playwright/test';

test.describe('Authentication Flow', () => {
  test('should login successfully', async ({ page }) => {
    await page.goto('/login');
    
    await page.fill('[data-testid="email"]', 'test@example.com');
    await page.fill('[data-testid="password"]', 'password123');
    
    await Promise.all([
      page.waitForNavigation(),
      page.click('[data-testid="login-button"]')
    ]);
    
    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
  });
  
  test('should show error with invalid credentials', async ({ page }) => {
    await page.goto('/login');
    
    await page.fill('[data-testid="email"]', 'invalid@example.com');
    await page.fill('[data-testid="password"]', 'wrongpassword');
    await page.click('[data-testid="login-button"]');
    
    await expect(page.locator('[data-testid="error-message"]'))
      .toContainText('Invalid credentials');
  });
});

// Visual regression test
test('homepage should look correct', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');
  
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    threshold: 0.3
  });
});

Hiring developers in Southeast Asia?

Get Cost Guide

More Interview Guides

How would you like to talk?

WhatsApp us Prefer texting at your own pace? Just hit us up on WhatsApp. We promise no spam and a hassle-free experience.

Loading available times…