Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

chrislove's avatar

"All Inertia requests must receive a valid Inertia response, however a plain JSON response was received."

Hi,

I have an inertia + react laravel app where I sometimes run into this issue. It's usually directly after a user logs in, and I believe related to fetch/axios requests firing before inertia is fully initialised. E.g. fetching a users notifications, or loading comments on a page.

"All Inertia requests must receive a valid Inertia response, however a plain JSON response was received."

Followed by a JSON dump of whatever request was being made. It can be hard to replicate but it seems to be related to post-login redirects.

Does anyone know what exactly causes this? I have tried to add some basic timeouts before non-inertia requests fire, but it still happens here and there.

From everything I have read (including from the creator of inertia), there should be no issue firing off fetch/axios requests, and I can't find anything solid about resolving this issue.

Is there a simple/best practice way I can check inertia has fully initialised, or a way to exclude these requests from being picked up by inertia?

Thanks

0 likes
14 replies
k_nadam's avatar

Are you using useForm hook or inertia's router for a request that returns a JSON? if your API endpoint returns a JSON you must use fetch/AXIOS.

1 like
chrislove's avatar

@k_nadam I can't see anywhere that this would be happening. I'll try and have another review this afternoon

JussiMannisto's avatar

I believe related to fetch/axios requests firing before inertia is fully initialised

This is not the case. Normal JS requests can send and receive whatever they want. This error only comes from Inertia requests: router methods, form helper methods, and Link components. If one of these gets a non-Inertia response, e.g. after a redirection or an error, then Inertia can't work with the response.

chrislove's avatar

@JussiMannisto

hmmm okay I will keep looking. I just had this pop up again a moment ago, still don't know exactly how to replicate.

here's the error i saw:

All Inertia requests must receive a valid Inertia response, however a plain JSON response was received. {"notifications":[],"unread_count":0}

And here's where the spaghetti that's being called:

useEffect(() => {
    // Fetch unread notifications count periodically
    const fetchUnreadCount = async () => {
        try {
            const response = await fetch(route('api.notifications.unread', { limit: 1 }));
            if (!response.ok) {
                throw new Error('Failed to fetch notifications');
            }
            const data = await response.json();
            setCount(data.unread_count);
        } catch (error) {
            console.error('Error fetching notifications:', error);
        }
    };

    // Fetch every minute
    const interval = setInterval(fetchUnreadCount, 60000);

    return () => {
        clearInterval(interval);
    };
}, []);
chrislove's avatar

@swez_k

public function unread(Request $request): JsonResponse
{
    $limit = $request->input('limit', 20);
    $notifications = $this->notificationService->getUnread(Auth::user(), $limit);
    
    return response()->json([
        'notifications' => $notifications,
        'unread_count' => Notification::where('recipient_user_id', Auth::id())
            ->unread()
            ->notExpired()
            ->count()
    ]);
}
swez_k's avatar

@chrislove the issue is probably from a middleware

try adding this header to your request:

headers: {
  Accept: 'application/json',
  'X-Requested-With': 'XMLHttpRequest', 
},
JussiMannisto's avatar

@swez_k That doesn't matter. Nothing that happens during this request should throw an Inertia error because this isn't an Inertia request at all. It's a plain JS fetch call. If it were an Inertia request, the JSON response itself would throw the error.

@chrislove Are you sure you're not using the same api.notifications.unread route (or its URL) in an Inertia request somewhere, e.g. in a Link component, form, or a redirection? The JS code you posted can't cause this error.

1 like
chrislove's avatar

@JussiMannisto This only ever seems to happen after login – i dont know exactly what causes it though.

It just happened again and I added a flag to ensure im checking the correct controller methods. E.g.

/**
 * Get unread notifications for the authenticated user.
 */
public function unread(Request $request): JsonResponse
{
    $limit = $request->input('limit', 20);
    $notifications = $this->notificationService->getUnread(Auth::user(), $limit);
    
    return response()->json([
        'is_api_unread' => true,
        'notifications' => $notifications,
        'unread_count' => Notification::where('recipient_user_id', Auth::id())
            ->unread()
            ->notExpired()
            ->count()
    ]);
}

This is the only single location in the entire app where this route is called:

import React, { useState, useEffect } from 'react'; import { Link } from '@inertiajs/react'; import { Bell } from 'lucide-react';

const NotificationButton = ({ unreadCount = 0 }) => { const [count, setCount] = useState(unreadCount);

// Update count when unreadCount prop changes
useEffect(() => {
    setCount(unreadCount);
}, [unreadCount]);

useEffect(() => {
    // Fetch unread notifications count periodically
    const fetchUnreadCount = async () => {
        try {
            const response = await fetch(route('api.notifications.unread', { limit: 1 }));
            if (!response.ok) {
                throw new Error('Failed to fetch notifications');
            }
            const data = await response.json();
            setCount(data.unread_count);
        } catch (error) {
            console.error('Error fetching notifications:', error);
        }
    };

    // Fetch every minute
    const interval = setInterval(fetchUnreadCount, 60000);

    return () => {
        clearInterval(interval);
    };
}, []);

return (
    <Link
        href={route('notifications.index')}
        className="flex items-center justify-start gap-2 px-4 py-3 font-medium text-sm text-gray-500 hover:bg-gray-100"
    >
        <div className="relative">
            <Bell size={16} className="" />
        </div>
        <span className="text-sm font-medium">Notifications</span>
        {count > 0 && (
            <div className="bg-red-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">
                {count > 9 ? '9+' : count}
            </div>
        )}
        
    </Link>
);

};

export default NotificationButton;

It's definitely something related to login and perhaps lingering requests that get fired again after the session is reinitialised. Perhaps as this runs every 60 sec, if a session is destroyed while this is running in the client it may cause issues? Honestly giving me a bit of a headache. But it definitely seems like inertia is intercepting requests that it shouldnt be

JussiMannisto's avatar

@chrislove The error comes from Inertia's front-end library. It means the original request must have been triggered by Inertia. I don't see the useEffect code being the culprit. If you want confirmation, add another query parameter to the request in useEffect, e.g. useEffectDebug: 1, and add that to the JSON response on the backend. I suspect it'll be null when you get this error.

The error may be caused by a redirection somewhere, such as a middleware or the login method. Or there may be mistake in the with the route definitions, e.g. another route points to the unread method or has the same URL.

I'd start by checking the routes and the login method. Then I'd check any middleware that might be redirecting Inertia requests to non-Inertia endpoints. I'd also check how exceptions are handled.

If you don't have Telescope installed in the dev environment, you might want to install it. It makes it easier to track what happens leading up to the error.

P.S. You should validate the "limit" parameter. If bots start sending invalid values, you'll get exceptions. You should always validate client input.

chrislove's avatar

Hey everyone, hard to have certainty around this as the issue was so intermittent, but this is the latest on that request to fetch unread notifications, as suggested by @swez_k

// Update count when unreadCount prop changes
useEffect(() => {
    setCount(unreadCount);
}, [unreadCount]);

useEffect(() => {
    // Fetch unread notifications count periodically
    const fetchUnreadCount = async () => {
        try {
            const response = await fetch(route('api.notifications.unread', { limit: 1 }), {
                headers: {
                    Accept: 'application/json',
                    'X-Requested-With': 'XMLHttpRequest', 
                },
            });
            if (!response.ok) {
                throw new Error('Failed to fetch notifications');
            }
            const data = await response.json();
            setCount(data.unread_count);
        } catch (error) {
            console.error('Error fetching notifications:', error);
        }
    };

    // Fetch every minute
    const interval = setInterval(fetchUnreadCount, 60000);

    return () => {
        clearInterval(interval);
    };
}, []);

With this change in place, I haven't seen the Inertia error in a few weeks, so perhaps this fixed it. Hard to say for sure but if you're having this issue slap these headers in your axios/fetch and pray for the best.

chrislove's avatar

And for even more context, this only seemed to occur when there was an automatic redirect after session expiry and logging back into the app. So i think @jussimannisto had some good insight on where to look if you're having similar issues

Mega_Aleksandar's avatar

Hi @chrislove ,

Inertia is meant to be used with props - so that when you "reload" data, but not the whole page, you can just fetch a prop with the only: ['yourpropname'] and in the controller method you can then return Inertia::render('SamePage', ['yourpropname'=>$value]);. More here https://inertiajs.com/partial-reloads So, in other words, in your initial load, you set the 'yourpropname' to null or empty, then on the second visit you set the only option.

That being said, if you really need to do an async call without inertia, you can always use axios, but then you have to manually handle the response. axios.get('route that returns json').then((response) => { manual transformation of data to your vue/react props,states,properties });

Full example:

Best regards,

Aleksandar

Please or to participate in this conversation.