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

Swaz's avatar
Level 20

Filtering routes with Ziggy and Inertia

As we all know, Ziggy exposes all your routes in the html. I don't mind the routes being exposed to authenticated users, but I would like to hide them from guests.

So basically, I'm looking for a way to only expose the /login and /forgot-password routes to unauthenticated users. Then once they login, all routes can be exposed again.

I'm playing around with doing this in a middleware:

// app/Http/Middleware/Ziggy.php
class Ziggy
{
    protected $guestRoutes = [
        'login',
        'password.request',
        'password.email',
        'password.update',
        'password.reset',
    ];

    public function handle(Request $request, Closure $next)
    {
        if (auth()->guest()) {
            config(['ziggy.only' => $this->guestRoutes]);
        }

        return $next($request);
    }
}
// app/Http/Kernel.php
class Kernel extends HttpKernel
{   
    //...
    protected $middlewareGroups = [
        'web' => [
            //...
            \App\Http\Middleware\HandleInertiaRequests::class,
            \App\Http\Middleware\Test::class,
        ],
    ];
}

This kind of works... if you manually refresh the page, but it won't work if you login with Inertia.

Any idea how I might get this to work?

0 likes
13 replies
Swaz's avatar
Level 20

@MohamedTammam I tried placing it before and after. The problem is the @routes blade directive. It doesn't get refreshed with Inertia, only on manual page reload.

MohamedTammam's avatar

@Swaz If you're using Breeze, I managed to do something like what you want using HandleInertiaRequests middleware.

public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => function () use ($request) {
                if (auth()->check())
                    return array_merge((new Ziggy)->toArray(), [
                        'location' => $request->url(),
                    ]);
                else
                    return array_merge((new Ziggy)->filter(['login', 'register'])->toArray(), [
                        'location' => $request->url(),
                    ]);
            },
        ]);
    }

If you aren't using Breeze starter kit, the implementation should be the same. You pass your Ziggy routes in shared data with a conditional filter.

1 like
Swaz's avatar
Level 20

@MohamedTammam That filters the routes being shared with Inertia requests, but if you view source, all the routes are still exposed in the html via the @routes helper. I believe what @Niush said, would be the only way right now

Niush's avatar

The Middleware looks good.

Next step would be to implement something like this: https://github.com/tighten/ziggy#retrieving-ziggys-routes-and-config-from-an-api-endpoint

  • First update the composer Ziggy package to latest version.
  • Create a new route preferably in web.php to return Ziggy object.
// web.php

use Tightenco\Ziggy\Ziggy;

Route::get('api/ziggy', fn () => response()->json(new Ziggy));
  • After Login is successful, in for example the onSuccess callback call the ziggy API and update the local data:
const submit = () => {
    form.post(route('login'), {
        onFinish: () => form.reset('password'),
        onSuccess: async () => {
          const response = await fetch("/api/ziggy", {
            headers: {
              Accept: "application/json",
            },
          });
          const routes = await response.json();
          
          // console.log(res);
      
          Ziggy.routes = routes.routes;
          Ziggy.defaults = routes.defaults;
        }
    });
};
  • Ziggy is the global variable, that is already initizlized by @routes in your app blade. This updated Ziggy data is then used by the route function.

But, the only problem is when form.post() is submitted, it redirects to next page (e.g. dashboard), where you might see Error: Ziggy error: route 'dashboard' is not in the route list., as it has not been refreshed yet. Would be an awesome feature, if history route only changed after onSuccess is finished running.

So, the only good solution at the moment, is to instead use Axios or fetch for calling the login route. Then, return a success JSON response on login (instead of redirecting). Then call the ziggy refreshing API. Then manually perform visit to dashboard page. Something like this:

const submit = () => {
    axios.post(route('login'), form.data()).then(async (res) => {
      form.reset('password');
      axios.get("/api/ziggy").then((routes) => {
        Ziggy.routes = routes.data.routes;
        Ziggy.defaults = routes.data.defaults;

        Inertia.visit(res.data.redirect_to ?? '/dashboard');
        // Login controller returns ["redirect_to" => RouteServiceProvider::HOME]
      });
    })
};

Similar thing can be done after Logout as well.

2 likes
Swaz's avatar
Level 20

@Niush Thanks for taking the time to write this up. I was able to get it working, I just wish there was a cleaner way to do this.

I ended up keeping all my routes exposed because my project is using Jetstream, and it's just another layer of complexity with optional 2FA that I don't want to deal with right now.

1 like
Swaz's avatar
Swaz
OP
Best Answer
Level 20

I found a better way to do this, here's instructions:

  1. Remove the @routes blade directive from resources/views/app.blade.php.
  2. Install npm install ziggy-js
  3. Removed everything related to ziggy in resources/js/app.js
  4. Defined your "groups" in app/config/ziggy.php.
return [
    'groups' => [
        'guest' => [
            'login', 'password.request', 'password.email', 
			'password.reset', 'password.update',
        ],
        'auth' => ['*'],
    ],
];
  1. Update app/Http/Middleware/HandleInertiaRequests.php with logic to determine what "group" to filter by.
//..
use Tightenco\Ziggy\Ziggy;

class HandleInertiaRequests extends Middleware
{
    // ...
    public function share(Request $request): array
    {
        $array = array_merge(parent::share($request), [
            'ziggy' => (new Ziggy('guest'))->toArray(),
        ]);

        if (auth()->guest()) {
            return $array;
        }

        return array_merge($array, [
            'ziggy' => (new Ziggy('auth'))->toArray(),
        ]);
    }
}
  1. Create a new "route" composable: resources/js/Composables/Route.js
import { usePage } from '@inertiajs/inertia-vue3'
import route from 'ziggy-js'

export default function useRoute(
    name, params, absolute,
    config = usePage().props.value.ziggy
) {
    return route(name, params, absolute, config)
}

That's it! Example usage:

<template>
    <AppLayout>
        
        <div v-for="project in projects" :key="project.id">
            <Link :href="route('projects.show', project)">
                {{ project.name }}
            </Link>
        </div>
        
    </AppLayout>
</template>

<script setup>
import { Link } from '@inertiajs/inertia-vue3'
import route from '@/Composables/Route'
import AppLayout from '@/Layouts/AppLayout.vue'

defineProps({
    projects: Array,
})
</script>

Notes

  1. If anyone knows how to use this globally, without having to import in every file, please let me know.

  2. On step 5, I'm not sure how expensive it is to make these calls. You may want to consider caching them.

'ziggy' => (new Ziggy('guest'))->toArray() // cache this?
'ziggy' => (new Ziggy('auth'))->toArray() // cache this?
6 likes
lxchen's avatar

@Swaz I am currently using Inertia.js v1.0.14 and Ziggy v1.8.2. I utilize the built-in "route" composable without creating a new one by passing shared page data into ZiggyVue like this:

createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(ZiggyVue, props.initialPage.props.ziggy)
            .mount(el);

Full example (app.ts) :

import { createApp, h, DefineComponent } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob<DefineComponent>('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(ZiggyVue, props.initialPage.props.ziggy)
            .mount(el);
    },
    progress: {
        color: '#4B5563',
    },
});
pilus's avatar

Just in case this is still relevant, creating a custom LoginResponse & LogoutResponse classes so that Inertia did a full reload after login / logout should be much cleaner & simpler.

Use this snippet inside the custom {Login,Logout}Response to instruct inertia to do full reload:

// ...
    public function toResponse($request)
    {
        return $request->wantsJson()
            ? // see \Laravel\Fortify\Http\Responses\{Login,Logout}Response@toResponse
            : Inertia::location(Fortify::redirects('login')) // or 'logout'
        ;
    }
//...

In my case I need Ziggy routes to be different in public pages and authenticated pages. I also don't want to load recaptcha in all page, just the guest auth pages. Full reload after login / logout works for me.

Everything from the accepted answer above aside from: "creating new "route" composable" should still apply.

Hopefully this is helpful for someone.

Cheers.

dogma's avatar

I believe it is easier than you think if you check the docs:

Source:

Ziggy Docs Filtering with groups

Filtering with groups

You can also define groups of routes that you want make available in different places in your app, using a groups key in your config file:

// config/ziggy.php

return [
    'groups' => [
        'auth' => ['auth.*'],
        'guest' => ['guest.*'],
    ],
];

Then, you can expose a specific group by passing the group name into the @routes Blade directive:


{{-- guest.blade.php --}}

@routes('guest')

To expose multiple groups you can pass an array of group names:

{{-- auth.blade.php --}}

@routes(['auth', 'guest'])

The only new thing is to change the root view of Inertia dynamically:

{{-- HandleInertiaRequests.php --}}

 public function rootView(Request $request)
    {
        // Bestimme das Layout basierend auf der Route oder einem anderen Kontext
        if ($request->routeIs('auth.*')) {
            return 'auth';
        }

        return 'guest';
    }

In your web.php:

Route::name('auth.')->group(function () {
All auth routes
});

Route::name('guest.')->group(function () {
All guest routes
});

lupinitylabs's avatar

@dogma If it only was that easy. Since the root view does not get re-evaluated dynamically, you will still have to do a hard reload the page after the conditional for the root view changes, e.g. after logging in, for the root view to switch. :-(

webmaxx's avatar

The easiest way

// config/ziggy.php

return [
    'except' => ['_debugbar.*'], // Used for all authenticated users
    'groups' => [
        'guest' => ['login'], // The routes are for guests only
    ],
];
// app/Http/Middleware/HandleInertiaRequests.php

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'auth' => [
            'user' => $request->user(),
        ],
        'ziggy' => fn () => [
            ...(new Ziggy(auth()->guest() ? 'guest' : null))->toArray(),
            'location' => $request->url(),
        ],
    ];
}												
// app.blade.php

@routes(auth()->guest() ? 'guest' : null)
// app/Http/Controllers/Auth/AuthenticatedSessionController.php

public function store(LoginRequest $request): HttpResponse
{
    $request->authenticate();
    $request->session()->regenerate();
    return inertia()->location(route('home')); // Full page reload
}

public function destroy(Request $request): RedirectResponse
{
    Auth::guard('web')->logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return inertia()->location(route('login')); // Full page reload
}
1 like
lupinitylabs's avatar

Yes, I found myself doing the same, in my case dependent on the authentication status, and using Fortify.

In case anyone is interested, with Fortify you would achieve that by registering a LoginResponse in the register() method of the FortifyServiceProvider like this:

    public function register()
    {
        $this->app->instance(LoginResponse::class, new class implements LoginResponse {
            public function toResponse($request)
            {
                return inertia()->location('/');
            }
        });
    }

If you care to remove the routes again after a user logs out, you can do the same with LogoutResponse.

@webmaxx I am not sure, however, why you both use the @routes() directive and pass the ziggy data on as shared data? The @routes() should be sufficient in your case.

Please or to participate in this conversation.