85

I'm using express as my custom server for next.js. Everything is fine, when I click the products to the list of products

Step 1: I click the product Link

enter image description here

Step 2: It will show the products in the database.

enter image description here

However if I refresh the /products page, I will get this Error

enter image description here

Server code (Look at /products endpoint)

app.prepare()
.then(() => {
  const server = express()

  // This is the endpoints for products
  server.get('/api/products', (req, res, next) => {
    // Im using Mongoose to return the data from the database
    Product.find({}, (err, products) => {
      res.send(products)
    })
  })

  server.get('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(3000, (err) => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})
.catch((ex) => {
  console.error(ex.stack)
  process.exit(1)
})

Pages - products.js (Simple layout that will loop the products json data)

import Layout from '../components/MyLayout.js'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'

const Products = (props) => (
  <Layout>
    <h1>List of Products</h1>
    <ul>
      { props.products.map((product) => (
        <li key={product._id}>{ product.title }</li>
      ))}
    </ul>
  </Layout>
)

Products.getInitialProps = async function() {

  const res = await fetch('/api/products')
  const data = await res.json()

  console.log(data)
  console.log(`Showed data fetched. Count ${data.length}`)

  return {
    products: data
  }
}

export default Products
sinusGob
  • 4,053
  • 12
  • 46
  • 82

14 Answers14

134

As the error states, you will have to use an absolute URL for the fetch you're making. I'm assuming it has something to do with the different environments (client & server) on which your code can be executed. Relative URLs are just not explicit & reliable enough in this case.

One way to solve this would be to just hardcode the server address into your fetch request, another to set up a config module that reacts to your environment:

/config/index.js

const dev = process.env.NODE_ENV !== 'production';

export const server = dev ? 'http://localhost:3000' : 'https://your_deployment.server.com';

products.js

import { server } from '../config';

// ...

Products.getInitialProps = async function() {

  const res = await fetch(`${server}/api/products`)
  const data = await res.json()

  console.log(data)
  console.log(`Showed data fetched. Count ${data.length}`)

  return {
    products: data
  }
}
Fabian Schultz
  • 18,138
  • 5
  • 49
  • 56
  • But how about when your data is really stored locally? I have a few localized static json's locally – PaulT Nov 04 '18 at 16:07
  • 3
    @PaulT: `'undefined' === typeof window ? 'http://localhost:3000' : 'https://your_deployment.server.com';` – cdoublev Nov 15 '18 at 17:51
22

Similar to the @Shanker's answer, but if you prefer not to install the additional package for this, here is how to do it.

async getInitialProps({ req }) {
    const protocol = req.headers['x-forwarded-proto'] || 'http'
    const baseUrl = req ? `${protocol}://${req.headers.host}` : ''

    const res = await fetch(baseUrl + '/api/products')
}
bubbleChaser
  • 755
  • 1
  • 8
  • 21
  • 3
    Is it safe to rely on HOST header? Headers are sent by client and can be anything. If a malicious code sends their server address in HOST they can get whatever data they want in your code. It depends of course what do you do with that data later, but I do believe that it opens doors that are better to stay closed. – Ahmad Shahwan Oct 24 '22 at 14:19
  • 1
    Yes, it is definitely not safe to rely on headers as it can be altered by man-in-the-middle attack. – Gomsy Feb 18 '23 at 00:03
13

It sounds silly but worth mentioning. If you're using SSR in your webapp the fetch call will work with a relative link on the client but will fail on the server. Only the server needs an absolute link!

If you want to prevent the server from making the request just wrap it in logic

if(global.window){
   const req = fetch('/api/test');
   ...
}
sidonaldson
  • 24,431
  • 10
  • 56
  • 61
6

This simple solution worked for me without having to add an additional config file,

Install

npm install --save next-absolute-url

Usage

import absoluteUrl from "next-absolute-url";

async getInitialProps({ req }){
  const { origin } = absoluteUrl(req, req.headers.host);
  console.log('Requested URL ->',origin); 
  // (or) other way
  const host = absoluteUrl(req, req.headers.host);
  console.log('Requested URL ->',host.origin); 
}
Shankar Ganesh Jayaraman
  • 1,401
  • 1
  • 16
  • 22
  • 3
    `req` will only be available on serverside, so you will need to add some checks/handling for the clientside rendering – Amr Saber Oct 19 '20 at 09:30
5

You could utilize environment variables if your project is hosted on a provider that supports it.

env.local

// Local
URL="http://localhost:3000"

// Production
URL="https://prod.com"

Then you can use the following.

const { URL } = process.env;
const data = await fetcher(URL + '/api');
Nick Winters
  • 51
  • 1
  • 2
3

Case 1. It's not an error. The isomorphic-unfetch is running by SSR mode, so Node.js needs to know the absolute url to fetch from it, because the back-end doesn't know your browser settings.

Case 2. Another scenario is to prevent the http host poisoning headers attack.

append secret keys and tokens to links containing it:

<a href="http://_SERVER['HOST']?token=topsecret">  (Django, Gallery, others)

....and even directly import scripts from it:

<script src="http://_SERVER['HOST']/misc/jquery.js?v=1.4.4">

Case 3. The isomorphic-unfetch it's the library we are going to use to fetch data. It's a simple implementation of the browser fetch API, but works both in client and server environments.

Read more about it:

  1. Isomorphic unfetch - Switches between unfetch & node-fetch for client & server
  2. Prevent http host headers attack
  3. Fetching Data for Pages
Francis Rodrigues
  • 1,470
  • 4
  • 25
  • 61
3

In the NextJS 9.5, we can also use process.cwd().
process.cwd() will give you the directory where Next.js is being executed.

import path from 'path'
import fs from "fs";

export const getStaticProps: GetStaticProps = async () => {
    const dataFilePath = path.join(process.cwd(), "jsonFiles", "data.json");
    console.log(dataFilePath);     // will be YourProject/jsonFiles/data.json

    const fileContents = fs.readFileSync(dataFilePath, "utf8");
    const data: TypeOfData[] = JSON.parse(fileContents);
    return { props: { data } };
};

Ref: https://nextjs.org/docs/basic-features/data-fetching#reading-files-use-processcwd

Nilay Mehta
  • 1,732
  • 2
  • 20
  • 26
2

Putting this out there because this showed up in google results for my problem, even though the question itself isn't really related (outside of the fact that the same dependency is throwing the same error message, albeit in a different context for a different reason).

I got this issue from using hardhat while attempting to verify (verify:verify) my contract on etherscan. The problem was that in the hardhat config, I didn't have a full url under rinkeby (since I was verifying on rinkeby, would be mainnet, etc.). Copy/pasting some config stuff quickly into a project I cloned from someone else, they had a full URL in their .env, while I had the url in the config and stored only my api key in my .env.

To figure this out, though, was straightforward--go into node_modules, then find the node-fetch folder, then lib, (this is from memory--just find the line that is vomitting in your stack trace) then the line number, and put a console log there to see what the "bad" url you're seeing is. Usually that's enough of a clue; in my case, it was an API key, obviously not a URL, and that made it straightforward to solve.

Kyle Baker
  • 3,424
  • 2
  • 23
  • 33
2

Make sure what the value of your API url is

In my case, I was using POST but my url was somewhat undefined.

Use console.log to see where is your request going.

Gabriel Arghire
  • 1,992
  • 1
  • 21
  • 34
1

this is a way to get the base hostname to fetch data from external endpoint without getting that error

function return_url(context) {
  if (process.env.NODE_ENV === "production") {
    return `https://${context.req.rawHeaders[1]}`;
  } else if (process.env.NODE_ENV !== "production") {
    return "http://localhost:3000";
  }
}

and on the getServerSideProps or getStaticProps functions you use

export async function getServerSideProps(context) {
  let url = return_url(context);
  const data = await fetch(`${url}/yourEndPoint`).then((res) => res.json());
  return {
    props: {
      data: data,
    },
  };
}
gouder hicham
  • 123
  • 10
0

If you have an absolute path issues. Try to use swr to access data.

Notice: This is a React hooks so you must call inside the component.

import useSWR from 'swr';

// optionally you can use unfetch package from npm or built yours to handle promise.
const fetcher = (...args) => fetch(...args).then(res => res.json())

export const AllProducts = () => {
  const { data, error } = useSWR('/api/products', fetcher)
  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  
  return (
    <div>{data}</div>
  );
};

Export or deploying in production

Whenever you are trying to deploy on Vercel you might encounter an error. For instance `

warn - Statically exporting a Next.js application via `next export` disables API routes`. 

It means you are trying to export data and NextJS does not support fetching data from pages/api/* directory. To avoid errors, its better to separate build and export command.

// package.json

{
 "scripts": {
    "dev": "next",
    "build": "next build",   // No next export command
    "start": "next start"
  },
}

Thanks folks for great contribution and I hope the answer shared will help somebody too.

Niyongabo Eric
  • 1,333
  • 18
  • 21
-1

If you are using next environment config prefix your variables with NEXT_PUBLIC_ as mentioned here Exposing Environment Variables to the Browser.

Zahema
  • 1,345
  • 2
  • 19
  • 35
-2

USE: NEXT_PUBLIC_STRAPI_URL="http://localhost:1337" instead of

NEXT_PUBLIC_STRAPI_URL=http://localhost:1337

-13

use .log(console.log) after nock , so you will get exact unmatched and expected url . Example:

     nock("http://localhost")
.log(console.log)
.persist()
.get("/api/config")
.reply(200, { data: 1234 })