next.js: Failed to parse URL when fetching localhost using Server Components

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: linux
      Arch: x64
      Version: #22 SMP Tue Jan 10 18:39:00 UTC 2023
    Binaries:
      Node: 16.17.0
      npm: 8.15.0
      Yarn: 1.22.19
      pnpm: 7.1.0
    Relevant packages:
      next: 13.3.1-canary.6
      eslint-config-next: 13.3.0
      react: 18.2.0
      react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue

https://codesandbox.io/p/github/Amirko28/next13-test/draft/intelligent-cherry?file=%2Fapp%2Fcomponents%2Fpost-list%2Findex.tsx&selection=[{"endColumn"%3A51%2C"endLineNumber"%3A2%2C"startColumn"%3A30%2C"startLineNumber"%3A2}]&workspace=%257B%2522activeFilepath%2522%253A%2522%252Fapp%252Fcomponents%252Fpost-list%252Findex.tsx%2522%252C%2522openFiles%2522%253A%255B%2522%252FREADME.md%2522%252C%2522%252Fapp%252Fapi%252Fposts%252FgetAll.ts%2522%252C%2522%252Fapp%252Fapi%252Fposts%252Fcreate.ts%2522%252C%2522%252Fapp%252Fcomponents%252Fpost-list%252Findex.tsx%2522%252C%2522%252Fapp%252Fcomponents%252Fadd-post%252Findex.tsx%2522%255D%252C%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522gitSidebarPanel%2522%253A%2522COMMIT%2522%252C%2522spaces%2522%253A%257B%2522clgf51ety000x356ilg8ae35d%2522%253A%257B%2522key%2522%253A%2522clgf51ety000x356ilg8ae35d%2522%252C%2522name%2522%253A%2522Default%2522%252C%2522devtools%2522%253A%255B%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A3000%252C%2522url%2522%253A%2522jiwe3j-3000.csb.app%2522%252C%2522key%2522%253A%2522clgf57pcp02n0356igg3gw2oz%2522%252C%2522isMinimized%2522%253Afalse%257D%252C%257B%2522type%2522%253A%2522TERMINAL%2522%252C%2522shellId%2522%253A%2522clgf53qdd0010g0i3cey03oke%2522%252C%2522key%2522%253A%2522clgf52ztc008e356ixn6ferp2%2522%252C%2522isMinimized%2522%253Afalse%257D%255D%257D%257D%252C%2522currentSpace%2522%253A%2522clgf51ety000x356ilg8ae35d%2522%252C%2522spacesOrder%2522%253A%255B%2522clgf51ety000x356ilg8ae35d%2522%255D%252C%2522hideCodeEditor%2522%253Afalse%257D

To Reproduce

run pnpm dev and go to port 3000 for it to crash in runtime

Describe the Bug

Calling fetch within a server component in order to get data from the same host fails: fetch('/api/posts') throws an error: TypeError: Failed to parse URL from /api/posts.

However, fetching the same URL within a client component works fine. Trying to access the resource with fetch('http://localhost:3000/api/posts') does work.

In the attached CodeSandbox repo: Fetch in a server component -> app/components/post-list/index.tsx - line 3 Fetch in a client component -> app/components/add-post/index.tsx - line 15

Expected Behavior

Calling fetch('/api/posts') should work inside server components

Which browser are you using? (if relevant)

Chrome 111.0.5563.147

How are you deploying your application? (if relevant)

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 18
  • Comments: 24 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I found this solution and it works fine:

async function getData() {
  const res = await import("../api/top-rated/route");  <---- this is the location of my api file

  return await (await res.GET()).json();
}
    const host = headers().get("host");

What if you’re not using Server Components and still getting the Failed to parse URL error?

if you are not using a server component you can directly call with a relative URL

fetch('/api');

I found this solution and it works fine:

async function getData() {
  const res = await import("../api/top-rated/route");  <---- this is the location of my api file

  return await (await res.GET()).json();
}

How do you pass query params in this case?

Got it. However, I could expect it to know the absolute URL when using dynamic data fetches, because Next knows the requested URL.

Besides that, I think throwing a more guiding error will help. Something like “Failed to parse URL. Pass an absolute URL when using server components”

async function getAllPosts(){
    const host = headers().get("host");
    const protocal = process?.env.NODE_ENV==="development"?"http":"https"
    let res = await fetch(`${protocal}://${host}/api`, { cache: "no-store" });
    let posts = res.json();
    return posts;
}

guys try this.

any update about it? I have found the same solution but methods needs request and param but they are sended by url

I found this solution and it works fine:

async function getData() {
  const res = await import("../api/top-rated/route");  <---- this is the location of my api file

  return await (await res.GET()).json();
}

How do you pass query params in this case?

Is it a valid way to fix this by using the host header to construct the absolute API URL? That should work both in development and production environment if I’m not mistaking.

import { headers } from "next/headers";

const fetchData = async (host: string) => {
  const res = await fetch(`http://${host}/api/quizzes`);
  return res.json();
};

export default async function Dashboard() {
  const host = headers().get("host");
  const data = await fetchData(host!);

  return <div>{JSON.stringify(data)}</div>;
}

Yeah, but that’s a fetch, a native API to node, that’s crashing. Not sure it’s a good idea to hijack that.

Another framework, Nuxt, uses $fetch, made by them to allow for the request to call into the API route from the server. https://nuxt.com/docs/guide/concepts/server-engine#direct-api-calls

Without such helpers, the problem is that you’d be making yet another request round trip, when you are already at the server side.

So you should just invoke the data you want directly, that’s one of the reasons we are writing the components on the server.

That means connecting to your database or data endpoints from the server component without having to involve API routes at all.

I see, it’s true that the request is really redundant when I can just import the function directly, and it’s even typesafe because I don’t need to cast the response after fetching. Thanks 😃

If you run the node REPL, in node 18+, and write fetch("/api/route"), what would you expect? It is similar in Server Side code, namely GetServerSideProps, GetStaticProps, and Server Components.

That is to say that you need an absolute URL. The browser, per spec IIRC, assumes paths relative to the document base URL/origin or something like that.

It is a fun read 😅

This is even happening in client components, is that expected?

What is the correct way to “opt out” of code running on the server? Docs (https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering) seem to suggest that using the useSearchParams() hook will ensure components do not render on server, but that does not seem to be the case (on v13.4.7).

Because server components run on the server, they essentially act like a node server. That’s why requesting ‘/path’ won’t resolve (same behavior as a regular server). You need to specify absolute paths for the URL to be parsed (e.g ‘http://localhost/path’)

any update about it? I have found the same solution but methods needs request and param but they are sended by url

I found this solution and it works fine:

async function getData() {
  const res = await import("../api/top-rated/route");  <---- this is the location of my api file

  return await (await res.GET()).json();
}

How do you pass query params in this case?

I used the following technique and we can pass query params by adding question mark after the ‘api/test?id=123’. the url itself doesn’t matter as we know which api function we are calling and only require query params.

import { NextRequest } from ‘next/server’

import TestListClient from ‘./list’; async function getData() { const apis = await import(‘…/…/api/test/route’); const body = await (await apis.GET(new NextRequest(‘http://localhost/api/test’))).json() return body.data; }