14

I have a very simple NextJS 9.3.5 project. For now, it has a single pages/users and a single pages/api/users that retrieves all users from a local MongoDB table

It builds fine locally using 'next dev' But, it fails on 'next build' with ECONNREFUSED error

page/users

import fetch from "node-fetch"
import Link from "next/link"

export async function getStaticProps({ params }) {
  const res = await fetch(`http://${process.env.VERCEL_URL}/api/users`)
  const users = await res.json()
  return { props: { users } }
}

export default function Users({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          <Link href="/user/[id]" as={`/user/${user._id}`}>
            <a>{user.name}</a>
          </Link>
        </li>
      ))}
    </ul>
  );
}

pages/api/users

import mongoMiddleware from "../../lib/api/mongo-middleware";
import apiHandler from "../../lib/api/api-handler";

export default mongoMiddleware(async (req, res, connection, models) => {
  const {
    method
  } = req

  apiHandler(res, method, {
    GET: (response) => {
      models.User.find({}, (error, users) => {
        if (error) {
          connection.close();
          response.status(500).json({ error });
        } else {
          connection.close();
          response.status(200).json(users);
        }
      })
    }
  });
})

yarn build

yarn run v1.22.4
$ next build
Browserslist: caniuse-lite is outdated. Please run next command `yarn upgrade`
> Info: Loaded env from .env
Creating an optimized production build

Compiled successfully.

> Info: Loaded env from .env
Automatically optimizing pages ..
Error occurred prerendering page "/users". Read more: https://err.sh/next.js/prerender-error:
FetchError: request to http://localhost:3000/api/users failed, reason: connect ECONNREFUSED 127.0.0.1:3000

Any ideas what is going wrong ? particularly when it works fine with 'next dev' ?

Thank you.

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67
user2821200
  • 153
  • 1
  • 1
  • 7
  • What is different in your configuration between `dev` and `build`? Possibly a different container, different host, connecting to a different database server? – Joe Apr 28 '20 at 05:07
  • 4
    "You should not fetch an API route from getStaticProps " - Check this part of the doc: https://nextjs.org/docs/basic-features/data-fetching#write-server-side-code-directly (v9.3.6) – Ricardo Canelas Apr 30 '20 at 13:01
  • 2
    @RicardoCanelas, Aha, that's kind of important - and solves the issue.. Thanks ! – user2821200 Apr 30 '20 at 19:28
  • Super! I answered below as well. – Ricardo Canelas May 01 '20 at 09:46

2 Answers2

20

I tried the same few days ago and didn't work... because when we build the app, we don't have localhost available... check this part of the doc - https://nextjs.org/docs/basic-features/data-fetching#write-server-side-code-directly - that said: "You should not fetch an API route from getStaticProps..." -

(Next.js 9.3.6)

Ricardo Canelas
  • 2,280
  • 26
  • 21
  • 1
    Or you could do a direct query to your database/graphql from `getStaticProps` in order to make it work or just use `getServerSideProps`. – Alexander Kim May 13 '20 at 05:35
  • 1
    Another solution is: in the getStaticPaths return the 'path' as an empty array, then when building it won't call the localhost.. (but in that case you can't export with `yarn export`) – Ricardo Canelas May 13 '20 at 17:02
7

Just to be even more explicit on top of what Ricardo Canelas said:

When you do next build, Next goes over all the pages it detects that it can build statically, i.e. all pages that don't define getServerSideProps, but which possibly define getStaticProps and getStaticPaths.

To build those pages, Next calls getStaticPaths to decide which pages you want to build, and then getStaticProps to get the actual data needed to build the page.

Now, if in either of getStaticPaths or getStaticProps you do an API call, e.g. to a JSON backend REST server, then this will get called by next build.

However, if you've integrated both front and backend nicely into a single server, chances are that you have just quit your development server (next dev) and are now trying out a build to see if things still work as sanity check before deployment.

So in that case, the build will try to access your server, and it won't be running, so you get an error like that.

The correct approach is, instead of going through the REST API, you should just do database queries directly from getStaticPaths or getStaticProps. That code never gets run on the client anyways, only server, to it will also be slightly more efficient than doing a useless trip to the API, which then calls the database indirectly. I have a demo that does that here: https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app/blob/b34c137a9d150466f3e4136b8d1feaa628a71a65/lib/article.ts#L4

export const getStaticPathsArticle: GetStaticPaths = async () => {
  return {
    fallback: true,
    paths: (await sequelize.models.Article.findAll()).map(
      article => {
        return {
          params: {
            pid: article.slug,
          }
        }
      }
    ),
  }
}

Note how on that example, both getStaticPaths and getStaticProps (here generalized HoC's for reuse, see also: Module not found: Can't resolve 'fs' in Next.js application ) do direct database queries via sequelize ORM, and don't do any HTTP calls to the external server API.

You should then only do client API calls from the React components on the browser after the initial pages load (i.e. from useEffect et al.), not from getStaticPaths or getStaticProps. BTW, note that as mentioned at: What is the difference between fallback false vs true vs blocking of getStaticPaths with and without revalidate in Next.js SSR/ISR? reducing client calls as much as possible and prerendering on server greatly reduces application complexity.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • What's the difference between calling an API or database directly? Both are external calls and must be running at the Next build. Next should be agnostic on this – Anderson Silva Jul 19 '22 at 13:13
  • 1
    @AndersonSilva API requires you API server to be running, DB only requires the DB server to be running (usually automatic at startup on a sev machine, or no requirements for SQLite) – Ciro Santilli OurBigBook.com Jul 19 '22 at 13:43
  • But the problem is I do actually have my dev backend server running locally still. Can next not make local api calls while it's biulding? – NickJ Jan 17 '23 at 20:44
  • @NickJ it should work, it's just your standard JS code making the request in the end. I'd try to make the exact same requests from outside NodeJs or look for error messages of some kind. – Ciro Santilli OurBigBook.com Jan 17 '23 at 21:20
  • 1
    Thanks for more details! But then, What's the point of developing your own `/api` folder then? – m3.b Feb 17 '23 at 04:43
  • @m3.b I never understood the point of `/api` very well: why wouldn't you just pass something static directly through props? But I'm sure there is one. But I don't think it has anything to do with building. `/api` also has to be built exactly like pages I think. – Ciro Santilli OurBigBook.com Feb 17 '23 at 08:04