46

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.

For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.

From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?

The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.

I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.

home.js:

export default function Page({ prop1, prop2, prop3 }) {
        // render etc.
}

export async function getServerSideProps(context) {
  const session = await getSession(context)
  let data = null
  var aArray = [], bArray = [], cArray = []
  const { db } = await connectToDatabase()

  function shuffle(array) {
    var currentIndex = array.length, temporaryValue, randomIndex;
    while (0 !== currentIndex) {
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }
    return array;
  }

  if (session) {
    const hostname = process.env.NEXT_PUBLIC_SITE_URL
    const options = { headers: { cookie: context.req.headers.cookie } }
    const res = await fetch(`${hostname}/api/user`, options)
    const json = await res.json()
    if (json.data) { data = json.data }

    // do some math with data ...
    // connect to MongoDB and do some comparisons, etc.
Aryan Beezadhur
  • 4,503
  • 4
  • 21
  • 42
m4tt
  • 825
  • 1
  • 11
  • 28

3 Answers3

125

But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().

You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.

From Next.js getServerSideProps documentation:

It can be tempting to reach for an API Route when you want to fetch data from the server, then call that API route from getServerSideProps. This is an unnecessary and inefficient approach, as it will cause an extra request to be made due to both getServerSideProps and API Routes running on the server.

(...) Instead, directly import the logic used inside your API Route into getServerSideProps. This could mean calling a CMS, database, or other API directly from inside getServerSideProps.

(Note that the same applies when using getStaticProps/getStaticPaths methods)


Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.

Let's assume you have this simple API route.

// pages/api/user
export default async function handler(req, res) {
    // Using a fetch here but could be any async operation to an external source
    const response = await fetch(/* external API endpoint */)
    const jsonData = await response.json()
    res.status(200).json(jsonData)
}

You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.

// pages/api/user
export async function getData() {
    const response = await fetch(/* external API endpoint */)
    const jsonData = await response.json()
    return jsonData
}

export default async function handler(req, res) {
    const jsonData = await getData()
    res.status(200).json(jsonData)
}

But also allows you to re-use the getData function in getServerSideProps.

// pages/home
import { getData } from './api/user'

//...

export async function getServerSideProps(context) {
    const jsonData = await getData()
    //...
}
juliomalves
  • 42,130
  • 20
  • 150
  • 146
  • 1
    The export function is what I also needed elsewhere where I need a reusable function but no one suggested me to export it this way, thanks I'll try this out – m4tt Jan 17 '21 at 17:07
  • 1
    absolutely, another way I solved it in some cases was to use "useSWR" client-side which also caches automatically the requests. – m4tt Mar 23 '21 at 14:48
  • 4
    to the bang! frustrated whole day found this at last working getting the data right away in the box! why api/routes doesn't directly why need to feed it other way, what kind of approach they took to build up, I don't understand! everything is to feed making around around our heads! sorry this might be my frustrate comment! – Jatinder Jun 12 '21 at 18:54
  • 3
    it's so weird that this isn't clear from the otherwise excellent documentation – Franco Sep 16 '21 at 07:11
  • Next.js docs have now been updated to mention this: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops-or-api-routes. – juliomalves Feb 12 '22 at 15:09
  • 1
    Calling the API route from `getServerSideProps` can be useful when you're not using Node js but some other languages/framework for example Django or Laravel. – fahad shaikh Jun 09 '22 at 09:59
  • @fahadshaikh You're absolutely right, but this answer is specific to internal APIs to Next.js ([API routes](https://nextjs.org/docs/api-routes/introduction)). It's perfectly fine to call external endpoints from `getServerSideProps`. I'll add a note to make that clearer. – juliomalves Jun 09 '22 at 10:52
  • @juliomalves Does exporting a function from `api` folder reveal the function or the rest of the file's code on the client side? – Roi Jun 11 '22 at 09:23
  • 2
    @Roi Not by just exporting the function, only if you use that function in client-side code does it get exposed. – juliomalves Jun 11 '22 at 10:45
  • Reusing the logic is sadly not (easily) possible anymore with RSC and Route handlers: - API routes are based on Connect (like Express) - RSC can't access the full request - Route Handlers are based on the Web Platform (Response/Request) object So for instance calling Passport validation logic from an RSC is complicated, because you don't have a proper "req" and "res" anymore – Eric Burel May 24 '23 at 13:01
  • @EricBurel That's correct. My answer is very specific to the `pages` folder structure and `getServerSideProps`, and definitely doesn't apply to the `app` folder structure and RSCs. – juliomalves May 24 '23 at 14:23
0

You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps

As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution: npm i node-mocks-http

import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
  return (
    <section>
      <h2>Latest News</h2>
      <NewsCard news={news} />
    </section>
  );
}
export async function getServerSideProps() {
  let req = httpMocks.createRequest();
  let res = httpMocks.createResponse();
  async function callMids(req, res, index, ...mids) {
    index = index || 0;
    if (index <= mids.length - 1)
      await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
  }
  await callMids(
    req,
    res,
    null,
    dbConnectMid,
    logger,
    newsController.sendAllNews
  );
  return {
    props: { news: res._getJSONData() },
  };
}

important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error. Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link: next connet run method to call next api's in serverSideProps

mo3n
  • 1,522
  • 2
  • 10
  • 34
H.hashemi
  • 49
  • 5
  • The way you refactor your code to avoid making a request to an internal API is really up to you, based on the requirements you have. My point was that you shouldn't make a request against an internal API route from inside `getServerSideProps`. – juliomalves Apr 03 '22 at 21:16
-2

Just try to use useSWR, example below

import useSWR from 'swr'
import React from 'react';

//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());

const Categories = () => {
 //getting data and error
 const { data, error } = useSWR('/api/category/getCategories', fetcher)
 if (error) return <div>Failed to load</div>
 if (!data) return <div>Loading...</div>
 if (data){
   // {data} is completed, it's ok!
   //your code here to make something with {data}
   return (
      <div>
      //something here, example {data.name}
      </div>
   )
 }
}

export default Categories

Please notice, fetch only supports absolute URLs, it's why I don't like to use it.

P.S. According to the docs, you can even use useSWR with SSR.

ChilTest
  • 461
  • 5
  • 18