sinusGob
sinusGob

Reputation: 4313

Next.js - Error: only absolute urls are supported

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

Upvotes: 88

Views: 265209

Answers (16)

Nikola Mitic
Nikola Mitic

Reputation: 1492

If accepted answer does not work please check the following:

  • If your env variables are defined in Vercel like they should
  • If they are, make not defined them
  • Make sure to redeploy as those env are resolved during build time

Upvotes: 1

Konrad Grzyb
Konrad Grzyb

Reputation: 1995

Nextjs 13

In my case error occured only on build. The case was that I had .env.developmet only. I had to aslo create .env file.

Upvotes: 0

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

NEXT_PUBLIC_STRAPI_URL=http://localhost:1337

Upvotes: -2

Gabriel Arghire
Gabriel Arghire

Reputation: 2360

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.

Upvotes: 2

gouder hicham
gouder hicham

Reputation: 133

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

Upvotes: 1

Kyle Baker
Kyle Baker

Reputation: 3712

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.

Upvotes: 2

Zahema
Zahema

Reputation: 1415

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

Upvotes: -1

sidonaldson
sidonaldson

Reputation: 25294

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

Upvotes: 13

bubbleChaser
bubbleChaser

Reputation: 925

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

Upvotes: 24

Agent
Agent

Reputation: 1395

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.

Upvotes: 0

Nick Winters
Nick Winters

Reputation: 51

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

Upvotes: 5

Nilay Mehta
Nilay Mehta

Reputation: 1907

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

Upvotes: 4

Shankar Ganesh Jayaraman
Shankar Ganesh Jayaraman

Reputation: 1491

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

Upvotes: 6

Francis Batista
Francis Batista

Reputation: 1510

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

Upvotes: 3

Prajakta Salokhe
Prajakta Salokhe

Reputation: 1

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

Upvotes: -13

Fabian Schultz
Fabian Schultz

Reputation: 18556

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

Upvotes: 139

Related Questions