search
next star Featured

Fix: getServerSideProps is not supported in App Router Next.js error - Complete Guide

Complete guide to fix 'getServerSideProps is not supported in App Router' error in Next.js applications. Learn how to migrate from Pages Router to App Router and implement server-side rendering with the new API.

person By Gautam Sharma
calendar_today January 8, 2026
schedule 14 min read
Next.js App Router getServerSideProps Server-Side Rendering Migration React JavaScript

The ‘getServerSideProps is not supported in App Router’ error occurs when developers try to use the legacy getServerSideProps function from Next.js Pages Router in the newer App Router system. This error is common during migration from the traditional Pages Router to the modern App Router introduced in Next.js 13. Understanding and resolving this error is crucial for building modern Next.js applications that leverage the improved server-side rendering capabilities of the App Router.


Understanding the Problem

Next.js introduced the App Router as a more flexible and powerful alternative to the Pages Router. The App Router uses a file-based routing system in the app directory and introduces new server-side rendering patterns that replace the legacy data fetching methods like getServerSideProps, getStaticProps, and getInitialProps.

Key Differences Between Pages Router and App Router:

  1. File Structure: Pages Router uses pages directory, App Router uses app directory
  2. Data Fetching: Legacy functions vs. Server Components and async functions
  3. Component Architecture: Client components only vs. Server and Client components
  4. Rendering: Different approaches to static and dynamic rendering
  5. API Functions: getServerSideProps vs. async server components
  6. Caching: Different caching strategies and control mechanisms

Solution 1: Migrate to Server Components

The primary replacement for getServerSideProps in App Router is using Server Components with async functions.

❌ Without Proper Migration (Pages Router):

// pages/products/[id].js - ❌ Legacy approach
export async function getServerSideProps(context) {
  const { id } = context.params;
  
  // Fetch data from external API
  const res = await fetch(`https://api.example.com/products/${id}`);
  const product = await res.json();

  return {
    props: {
      product,
    },
  };
}

export default function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

✅ With Server Components (App Router):

app/products/[id]/page.js:

// ✅ Server Component with async function
async function fetchProduct(id) {
  // ✅ Server-side data fetching
  const res = await fetch(`https://api.example.com/products/${id}`, {
    headers: {
      'Cache-Control': 'no-store', // ✅ Disable caching if needed
    },
  });
  
  if (!res.ok) {
    throw new Error('Failed to fetch product');
  }
  
  return res.json();
}

// ✅ Async Server Component
export default async function ProductPage({ params }) {
  const { id } = params;
  const product = await fetchProduct(id);

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
      <p className="text-gray-600 mb-6">{product.description}</p>
      <div className="bg-gray-100 p-4 rounded-lg">
        <h2 className="text-xl font-semibold mb-2">Product Details</h2>
        <p>Price: ${product.price}</p>
        <p>Category: {product.category}</p>
      </div>
    </div>
  );
}

app/products/page.js:

// ✅ Server Component for product listing
async function fetchProducts() {
  const res = await fetch('https://api.example.com/products');
  
  if (!res.ok) {
    throw new Error('Failed to fetch products');
  }
  
  return res.json();
}

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-6">Products</h1>
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        {products.map((product) => (
          <div key={product.id} className="border rounded-lg p-4 hover:shadow-md transition-shadow">
            <h2 className="text-xl font-semibold mb-2">{product.name}</h2>
            <p className="text-gray-600 mb-2">${product.price}</p>
            <a 
              href={`/products/${product.id}`} 
              className="text-blue-500 hover:underline"
            >
              View Details
            </a>
          </div>
        ))}
      </div>
    </div>
  );
}

Solution 2: Using Server Actions and Data Fetching

For more complex data fetching scenarios, you can create dedicated server functions.

lib/api.js:

// ✅ Server functions for data fetching
export async function getServerSideData(url, options = {}) {
  const res = await fetch(url, {
    cache: options.cache || 'force-cache', // ✅ Control caching behavior
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!res.ok) {
    throw new Error(`HTTP error! status: ${res.status}`);
  }

  return res.json();
}

export async function getServerSidePropsWithAuth(url, token) {
  const res = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });

  if (!res.ok) {
    throw new Error('Authentication failed');
  }

  return res.json();
}

app/dashboard/page.js:

import { getServerSideData } from '../../lib/api';

// ✅ Server Component with custom data fetching
export default async function DashboardPage() {
  try {
    // ✅ Fetch user data
    const user = await getServerSideData('https://api.example.com/user');
    
    // ✅ Fetch dashboard data
    const dashboardData = await getServerSideData('https://api.example.com/dashboard');
    
    return (
      <div className="container mx-auto p-4">
        <h1 className="text-3xl font-bold mb-6">Dashboard</h1>
        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
          <div className="bg-white p-6 rounded-lg shadow-md">
            <h2 className="text-xl font-semibold mb-4">User Information</h2>
            <p><strong>Name:</strong> {user.name}</p>
            <p><strong>Email:</strong> {user.email}</p>
          </div>
          <div className="bg-white p-6 rounded-lg shadow-md">
            <h2 className="text-xl font-semibold mb-4">Statistics</h2>
            <p><strong>Total Items:</strong> {dashboardData.totalItems}</p>
            <p><strong>Active Users:</strong> {dashboardData.activeUsers}</p>
          </div>
        </div>
      </div>
    );
  } catch (error) {
    return (
      <div className="container mx-auto p-4">
        <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
          <h2 className="font-bold">Error Loading Dashboard</h2>
          <p>{error.message}</p>
        </div>
      </div>
    );
  }
}

Solution 3: Handling Dynamic Data with Revalidation

For data that needs to be refreshed periodically, use Next.js revalidation features.

app/products/[id]/page.js:

// ✅ Server Component with revalidation
async function fetchProductWithRevalidation(id) {
  const res = await fetch(`https://api.example.com/products/${id}`, {
    next: { 
      revalidate: 3600, // ✅ Revalidate every hour (3600 seconds)
    },
  });

  if (!res.ok) {
    throw new Error('Failed to fetch product');
  }

  return res.json();
}

export default async function ProductPage({ params }) {
  const { id } = params;
  const product = await fetchProductWithRevalidation(id);

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
      <p className="text-gray-600 mb-6">{product.description}</p>
      <div className="bg-gray-100 p-4 rounded-lg">
        <h2 className="text-xl font-semibold mb-2">Product Details</h2>
        <p>Price: ${product.price}</p>
        <p>Category: {product.category}</p>
        <p>Last updated: {new Date(product.updatedAt).toLocaleDateString()}</p>
      </div>
    </div>
  );
}

app/blog/[slug]/page.js:

// ✅ Server Component with conditional revalidation
async function fetchBlogPost(slug) {
  const res = await fetch(`https://api.example.com/blog/${slug}`, {
    next: { 
      revalidate: process.env.NODE_ENV === 'production' ? 3600 : 30, // ✅ Different revalidation times
    },
  });

  if (!res.ok) {
    if (res.status === 404) {
      return null; // ✅ Handle not found
    }
    throw new Error('Failed to fetch blog post');
  }

  return res.json();
}

export default async function BlogPostPage({ params }) {
  const { slug } = params;
  const post = await fetchBlogPost(slug);

  if (!post) {
    return (
      <div className="container mx-auto p-4">
        <h1 className="text-3xl font-bold mb-6">Blog Post Not Found</h1>
        <p>The requested blog post could not be found.</p>
      </div>
    );
  }

  return (
    <div className="container mx-auto p-4 max-w-3xl">
      <article>
        <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
        <p className="text-gray-600 mb-6">By {post.author} on {new Date(post.publishedAt).toLocaleDateString()}</p>
        <div className="prose max-w-none">
          <div dangerouslySetInnerHTML={{ __html: post.content }} />
        </div>
      </article>
    </div>
  );
}

Solution 4: Client-Side Data Fetching with Server Components

For scenarios where you need both server and client-side data, combine Server Components with Client Components.

app/products/[id]/page.js:

import ProductDetails from './ProductDetails';
import { Suspense } from 'react';

// ✅ Server Component that fetches main data
async function fetchProduct(id) {
  const res = await fetch(`https://api.example.com/products/${id}`);
  
  if (!res.ok) {
    throw new Error('Failed to fetch product');
  }
  
  return res.json();
}

export default async function ProductPage({ params }) {
  const { id } = params;
  const product = await fetchProduct(id);

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
      <p className="text-gray-600 mb-6">{product.description}</p>
      
      {/* ✅ Server Component for static data */}
      <div className="bg-gray-100 p-4 rounded-lg mb-6">
        <h2 className="text-xl font-semibold mb-2">Product Details</h2>
        <p>Price: ${product.price}</p>
        <p>Category: {product.category}</p>
      </div>
      
      {/* ✅ Client Component for dynamic data */}
      <Suspense fallback={<div>Loading reviews...</div>}>
        <ProductDetails productId={product.id} />
      </Suspense>
    </div>
  );
}

app/products/[id]/ProductDetails.js:

'use client';

import { useState, useEffect } from 'react';

// ✅ Client Component for dynamic data
export default function ProductDetails({ productId }) {
  const [reviews, setReviews] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchReviews = async () => {
      try {
        const res = await fetch(`/api/reviews/${productId}`);
        const data = await res.json();
        setReviews(data);
      } catch (error) {
        console.error('Error fetching reviews:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchReviews();
  }, [productId]);

  if (loading) {
    return <div>Loading reviews...</div>;
  }

  return (
    <div className="bg-white p-6 rounded-lg shadow-md">
      <h2 className="text-xl font-semibold mb-4">Customer Reviews</h2>
      {reviews.length > 0 ? (
        <div className="space-y-4">
          {reviews.map((review) => (
            <div key={review.id} className="border-b pb-4 last:border-b-0">
              <h3 className="font-medium">{review.title}</h3>
              <p className="text-gray-600">{review.comment}</p>
              <p className="text-sm text-gray-500">By {review.author} on {review.date}</p>
            </div>
          ))}
        </div>
      ) : (
        <p>No reviews yet.</p>
      )}
    </div>
  );
}

Solution 5: Error Handling and Fallbacks

Proper error handling is crucial when migrating from getServerSideProps.

app/error.js:

'use client';

import { useEffect } from 'react';

// ✅ Global error component
export default function GlobalError({ error, reset }) {
  useEffect(() => {
    // ✅ Log error to an error reporting service
    console.error('Global error:', error);
  }, [error]);

  return (
    <div className="container mx-auto p-4">
      <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
        <h2 className="font-bold text-xl">Something went wrong!</h2>
        <p className="mb-4">{error.message}</p>
        <button
          onClick={() => reset()}
          className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
        >
          Try again
        </button>
      </div>
    </div>
  );
}

app/products/[id]/page.js:

// ✅ Server Component with error handling
async function fetchProductWithErrorHandling(id) {
  try {
    const res = await fetch(`https://api.example.com/products/${id}`, {
      next: { revalidate: 3600 },
    });

    if (!res.ok) {
      if (res.status === 404) {
        // ✅ Handle not found specifically
        return { notFound: true };
      }
      throw new Error(`Failed to fetch product: ${res.status}`);
    }

    return await res.json();
  } catch (error) {
    console.error('Error fetching product:', error);
    // ✅ Return error state instead of throwing
    return { error: error.message };
  }
}

export default async function ProductPage({ params }) {
  const { id } = params;
  const result = await fetchProductWithErrorHandling(id);

  if (result.notFound) {
    return (
      <div className="container mx-auto p-4">
        <h1 className="text-3xl font-bold mb-6">Product Not Found</h1>
        <p>The requested product could not be found.</p>
      </div>
    );
  }

  if (result.error) {
    return (
      <div className="container mx-auto p-4">
        <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
          <h2 className="font-bold">Error Loading Product</h2>
          <p>{result.error}</p>
        </div>
      </div>
    );
  }

  const product = result;

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
      <p className="text-gray-600 mb-6">{product.description}</p>
      <div className="bg-gray-100 p-4 rounded-lg">
        <h2 className="text-xl font-semibold mb-2">Product Details</h2>
        <p>Price: ${product.price}</p>
        <p>Category: {product.category}</p>
      </div>
    </div>
  );
}

Working Code Examples

Complete Migration Example:

app/users/[id]/page.js:

// ✅ Complete example of migrating from getServerSideProps
import { notFound } from 'next/navigation';

// ✅ Server function for data fetching
async function getUser(id) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
    next: { revalidate: 3600 }, // ✅ Cache for 1 hour
  });

  if (!res.ok) {
    return null;
  }

  return res.json();
}

async function getUserPosts(userId) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`, {
    next: { revalidate: 1800 }, // ✅ Cache for 30 minutes
  });

  if (!res.ok) {
    return [];
  }

  return res.json();
}

export default async function UserProfile({ params }) {
  const { id } = params;
  
  // ✅ Fetch user data
  const user = await getUser(id);
  
  if (!user) {
    notFound(); // ✅ Use Next.js notFound function
  }

  // ✅ Fetch related data
  const posts = await getUserPosts(id);

  return (
    <div className="min-h-screen bg-gray-50">
      <div className="container mx-auto px-4 py-8">
        <div className="bg-white rounded-lg shadow-md p-6 mb-6">
          <h1 className="text-3xl font-bold mb-2">{user.name}</h1>
          <p className="text-gray-600 mb-2">{user.email}</p>
          <p className="text-gray-600">{user.company.name}</p>
        </div>

        <div className="bg-white rounded-lg shadow-md p-6">
          <h2 className="text-2xl font-semibold mb-4">Recent Posts</h2>
          {posts.length > 0 ? (
            <div className="space-y-4">
              {posts.slice(0, 5).map((post) => (
                <div key={post.id} className="border-b pb-4 last:border-b-0">
                  <h3 className="font-medium text-lg">{post.title}</h3>
                  <p className="text-gray-600 mt-1">{post.body}</p>
                </div>
              ))}
            </div>
          ) : (
            <p>No posts available.</p>
          )}
        </div>
      </div>
    </div>
  );
}

API Route for Client-Side Data (app/api/reviews/[productId]/route.js):

import { NextResponse } from 'next/server';

// ✅ API route for client-side data fetching
export async function GET(request, { params }) {
  const { productId } = params;
  
  try {
    // ✅ Fetch reviews from external API
    const res = await fetch(`https://api.example.com/reviews/${productId}`);
    
    if (!res.ok) {
      return NextResponse.json(
        { error: 'Failed to fetch reviews' }, 
        { status: res.status }
      );
    }
    
    const reviews = await res.json();
    
    return NextResponse.json(reviews);
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal server error' }, 
      { status: 500 }
    );
  }
}

Best Practices for Migration

1. Identify Legacy Data Fetching Functions

// ✅ Before migration - identify all getServerSideProps
// Search for patterns in your codebase:
// - getServerSideProps
// - getStaticProps  
// - getInitialProps

2. Convert to Server Components

// ✅ Good practice - async server components
export default async function MyPage({ params }) {
  const data = await fetchData(params.id);
  return <div>{data.content}</div>;
}

3. Use Proper Error Handling

// ✅ Good practice - error handling
export default async function MyPage({ params }) {
  try {
    const data = await fetchData(params.id);
    return <div>{data.content}</div>;
  } catch (error) {
    return <div>Error: {error.message}</div>;
  }
}

4. Implement Caching Strategies

// ✅ Good practice - caching control
const res = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 } // ✅ Cache for 1 hour
});

5. Separate Server and Client Logic

// ✅ Good practice - component separation
// Server Component: app/page.js
// Client Component: components/InteractiveComponent.js

Debugging Steps

Step 1: Identify Legacy Functions

# Search for legacy data fetching functions
grep -r "getServerSideProps\|getStaticProps\|getInitialProps" .

Step 2: Check App Router Setup

# Verify app directory structure
ls -la app/

Step 3: Test Server Components

// Add logging to verify server component execution
console.log('Server component executed');

Step 4: Verify Data Fetching

// Test data fetching in server components
const data = await fetch('...');
console.log('Data fetched:', data);

Step 5: Check Error Boundaries

// Ensure proper error handling
try {
  const data = await fetchData();
} catch (error) {
  console.error('Error:', error);
}

Common Mistakes to Avoid

1. Using Legacy Functions in App Router

// ❌ Don't do this in App Router
export async function getServerSideProps() {
  // ❌ This will cause an error
}

2. Forgetting ‘use client’ Directive

// ❌ Don't forget the directive for client components
'use client'; // ✅ Add this when needed

3. Not Handling Async Server Components Properly

// ❌ Don't treat server components as sync
function MyPage({ params }) {
  const data = await fetchData(params.id); // ❌ Can't use await in sync function
  return <div>{data}</div>;
}

// ✅ Do this instead
export default async function MyPage({ params }) {
  const data = await fetchData(params.id);
  return <div>{data}</div>;
}

4. Ignoring Caching Behavior

// ❌ Don't ignore caching
const res = await fetch('https://api.example.com/data');
// ✅ Consider caching needs
const res = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 }
});

5. Mixing Server and Client Logic Improperly

// ❌ Don't mix server and client logic in the same component
export default async function MixedComponent() {
  // ❌ Server logic
  const serverData = await fetch('...');
  
  // ❌ Client logic
  const [clientState, setClientState] = useState(); // ❌ Can't use hooks in server components
  
  return <div>{serverData}</div>;
}

Performance Considerations

1. Leverage Server-Side Rendering

// ✅ Good for performance
export default async function ProductPage({ params }) {
  // ✅ Data fetching happens on server
  const product = await fetchProduct(params.id);
  return <div>{product.name}</div>;
}

2. Use Streaming for Large Data Sets

// ✅ Good for large datasets
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<div>Loading content...</div>}>
        <LargeContent />
      </Suspense>
    </div>
  );
}

3. Optimize Caching Strategies

// ✅ Good caching strategy
const res = await fetch('https://api.example.com/data', {
  next: { 
    revalidate: process.env.NODE_ENV === 'production' ? 3600 : 30,
    tags: ['products']
  }
});

Security Considerations

1. Validate Server-Side Data

// ✅ Validate data before rendering
async function fetchUserData(id) {
  const res = await fetch(`https://api.example.com/users/${id}`);
  const user = await res.json();
  
  // ✅ Validate user data
  if (!user || !user.id) {
    throw new Error('Invalid user data');
  }
  
  return user;
}

2. Sanitize External Data

// ✅ Sanitize data from external sources
import DOMPurify from 'isomorphic-dompurify';

async function fetchContent() {
  const res = await fetch('https://api.example.com/content');
  const content = await res.json();
  
  // ✅ Sanitize HTML content
  return {
    ...content,
    sanitizedContent: DOMPurify.sanitize(content.html)
  };
}

Testing Migration Code

1. Test Server Component Rendering

// test/server-component.test.js
describe('Server Component', () => {
  it('should render with server data', async () => {
    // Test that server component renders correctly with data
  });
});

2. Test Data Fetching

// test/data-fetching.test.js
describe('Data Fetching', () => {
  it('should fetch data correctly in server component', async () => {
    // Test data fetching logic
  });
});

Alternative Solutions

1. Continue Using Pages Router

// ✅ Option: Keep using Pages Router for specific routes
// Keep pages/ directory for legacy routes
// Use app/ directory for new features

2. Hybrid Approach

// ✅ Option: Gradual migration
// Migrate pages gradually
// Keep both routing systems during transition
Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

next

Fix: Next.js App Router API route not working error

Quick fix for Next.js App Router API route not working error. Learn how to properly create and use API routes in the App Router.

January 8, 2026
next

Fix: cookies() can only be used in Server Components error Next.js

Quick fix for 'cookies() can only be used in Server Components' error in Next.js. Learn how to properly use cookies in Server Components.

January 8, 2026
next

Fix: document is not defined in Next.js Error - Complete Client-Side Guide

Complete guide to fix 'document is not defined' error in Next.js applications. Learn how to handle browser APIs safely in server-side rendering environments.

January 8, 2026