Top 15 Next.js Interview Coding Challenges with Solutions

Top 15 Next.js Interview Coding Challenges with Solutions [2025] 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 […]

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

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 (
    

{post.title}

); } 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 (
    

Our Products

{products.map((product) => (
{product.name}

{product.name}

${product.price}

{product.description}

))}
); } 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 

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 (
    
      
{children}
); } 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 ( ); }

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 (
    
      
{images.map((image) => (
setSelectedImage(image)} > {image.alt}
))}
{selectedImage && (
setSelectedImage(null)}>
{selectedImage.alt}
)} > ); }

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.title}

By {article.author}
); } 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 (
      
        
          
          
          
          
          
          {/* Structured Data */}
          
        
        
          
); } } 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: () => 
Loading chart...
, 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 || (
        

Something went wrong

); } return this.props.children; } } // pages/_error.js function ErrorPage({ statusCode }) { return (

{statusCode ? `Server Error ${statusCode}` : 'Client Error'}

{statusCode === 404 ? 'This page could not be found.' : 'An error occurred. Please try again later.'}

); } 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
  });
});

Remote hiring made easy

75%
faster to hire
58%
cost savings
2K+
hires made

Our Offices

415 Mission St, San Francisco,
CA 94105, United States

320 Serangoon Road #13-05, Centrium Square, Singapore 218108

6/F, SAVISTA Realty Building, Binh Thanh, Ho Chi Minh City, Vietnam

12/F, Honest Building, 09-11 Leighton Rd, Causeway Bay, Hong Kong

Nongsa Digital Park, Jalan Hang Lekiu, Kota Batam, Provinsi Kepulaunan Riau, 29466

Level 16, Menara Etiqa, No. 3, Jalan Bangsar Utama 1, 59000, Kuala Lumpur, Malaysia

2/F, JKSA Building, 4954-A A. Arnaiz Ave. cor. Mayor St., Pio del Pilar, Makati City, Philippines