Failed to Parse URL

Used in this guide:
Next.js 15.3.4

Introduction

This error occurrs when a relative URL is used in a server environment

Error:

Failed to parse URL from /api/web-users/me


In this example, fetch('/api/web-users/me', {...}) is being run in an environment that does not recognize relative URLs - most likely on the server side (e.g., in a Next.js server component or a server utility).


Why this happen?

In Node.js (or server-side code), the fetch function requires an absolute URL like:

fetch('http://localhost:3000/api/web-users/me')

But relative URL (/api/web-users/me) only works in the browser environment.




Error Example:

lib/payload/getOwnUser.ts
import { WebUser } from "@/payload-types";
 
export async function getOwnUser(): Promise<{ user: WebUser | null; error: string | null }> {
    try {
        const res = await fetch(`/api/web-users/me`, { // <- using relative URL in a server environment
            method: "GET",
            headers: {
                "Content-Type": "application/json",
            },
            credentials: "include",
        });
 
        if (!res.ok) {
            throw new Error(`Error in try: ${res.status} ${res.statusText}`);
        }
 
        const data = await res.json();
        return { 
                user: data.user,
                error: null 
        };
    } catch (error) {
        console.error("Error in catch: ", (error as Error).message);
        return {
            user: null,
            error: (error as Error).message,
        }
    }
}

Solution:

If this needs to run on the server, update the URL to an absolute URL:

lib/payload/getOwnUser.ts
import { WebUser } from "@/payload-types";
 
export async function getOwnUser(): Promise<{ user: WebUser | null; error: string | null }> {
    try {
 
        const rootUrl = process.env.NODE_ENV === "development"
            ? process.env.NEXT_PUBLIC_ROOT_URL_DEV
            : process.env.NEXT_PUBLIC_ROOT_URL_PROD;
 
        const res = await fetch(`${rootUrl}/api/web-users/me`, {
            method: "GET",
            headers: {
                "Content-Type": "application/json",
            },
            credentials: "include",
        });
 
        if (!res.ok) {
            throw new Error(`Error in try: ${res.status} ${res.statusText}`);
        }
 
        const data = await res.json();
        return { 
                user: data.user ?? null,
                error: null 
        };
    } catch (error) {
        console.error("Error in catch: ", (error as Error).message);
        return {
            user: null,
            error: (error as Error).message,
        }
    }
}

Bonus: Using the getOwnUser function

"use client"
import { useEffect, useState } from "react"
import { getOwnUser } from "@/lib/payload/getOwnUser"
import { WebUser } from "@/payload-types"
 
export default function TestPageClient() {
    const [user, setUser] = useState<WebUser | null>(null)
 
    useEffect(() => {
        const fetchOwnUser = async () => {
            const { user, error } = await getOwnUser();
            console.log(user);
            console.log(error);
            setUser(user);
        }
        fetchOwnUser();
    }, [])
 
    return (
        <main>
            <h1>Test Page</h1>
            <p>Name: {user?.firstName}</p>
        </main>
    )
}

JKT

Stay focused, and the rest will follow

©Jakkrit Turner. All rights reserved