Skip to main content

Installation

Ziggy includes a useRoute() hook that makes it easy to use the route() helper in your React components.

Basic Setup with @routes

If you’re using the @routes Blade directive, Ziggy’s config is available globally. Simply import and use the hook:
import React from 'react';
import { useRoute } from 'ziggy-js';

export default function PostsLink() {
    const route = useRoute();

    return <a href={route('posts.index')}>All Posts</a>;
}

Setup Without @routes

If you’re not using @routes, import Ziggy’s configuration and pass it to useRoute():
import React from 'react';
import { useRoute } from 'ziggy-js';
import { Ziggy } from './ziggy.js'; // Generated by php artisan ziggy:generate

export default function PostsLink() {
    const route = useRoute(Ziggy);

    return <a href={route('posts.index')}>All Posts</a>;
}

Making Config Global

To avoid passing Ziggy to every useRoute() call, make it available globally:
// app.js or index.js
import { Ziggy } from './ziggy.js';

// Make Ziggy globally available
globalThis.Ziggy = Ziggy;
Now you can use useRoute() without passing the config:
import { useRoute } from 'ziggy-js';

export default function Navigation() {
    const route = useRoute(); // No config needed

    return (
        <nav>
            <a href={route('home')}>Home</a>
            <a href={route('about')}>About</a>
        </nav>
    );
}

Basic Usage

import React from 'react';
import { useRoute } from 'ziggy-js';

export default function Navigation() {
    const route = useRoute();

    return (
        <nav className="main-nav">
            <a href={route('home')} className="nav-link">Home</a>
            <a href={route('posts.index')} className="nav-link">Blog</a>
            <a href={route('contact')} className="nav-link">Contact</a>
        </nav>
    );
}
import React from 'react';
import { useRoute } from 'ziggy-js';

export default function PostCard({ post }) {
    const route = useRoute();

    return (
        <article>
            <h2>{post.title}</h2>
            <p>{post.excerpt}</p>
            <a href={route('posts.show', { post: post.id })}>
                Read more →
            </a>
        </article>
    );
}

Dynamic Lists

import React from 'react';
import { useRoute } from 'ziggy-js';

export default function PostList({ posts }) {
    const route = useRoute();

    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>
                    <a href={route('posts.show', { post: post })}>
                        {post.title}
                    </a>
                </li>
            ))}
        </ul>
    );
}

React Router Integration

Use Ziggy with React Router for client-side navigation:
import React from 'react';
import { Link } from 'react-router-dom';
import { useRoute } from 'ziggy-js';

export default function Navigation() {
    const route = useRoute();

    return (
        <nav>
            <Link to={route('home', [], false)}>Home</Link>
            <Link to={route('posts.index', [], false)}>Blog</Link>
            <Link to={route('about', [], false)}>About</Link>
        </nav>
    );
}
Pass false as the third argument to route() to generate relative URLs without the domain.

Form Handling

POST Requests with axios

import React, { useState } from 'react';
import { useRoute } from 'ziggy-js';
import axios from 'axios';

export default function CreatePost() {
    const route = useRoute();
    const [post, setPost] = useState({ title: '', body: '' });
    const [loading, setLoading] = useState(false);

    const handleSubmit = async (e) => {
        e.preventDefault();
        setLoading(true);

        try {
            const response = await axios.post(route('posts.store'), post);
            console.log('Post created:', response.data);
            // Redirect to the new post
            window.location.href = route('posts.show', { post: response.data.id });
        } catch (error) {
            console.error('Error creating post:', error);
        } finally {
            setLoading(false);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                value={post.title}
                onChange={(e) => setPost({ ...post, title: e.target.value })}
                placeholder="Post title"
                required
            />
            <textarea
                value={post.body}
                onChange={(e) => setPost({ ...post, body: e.target.value })}
                placeholder="Post content"
                required
            />
            <button type="submit" disabled={loading}>
                {loading ? 'Creating...' : 'Create Post'}
            </button>
        </form>
    );
}

Update Requests

import React, { useState } from 'react';
import { useRoute } from 'ziggy-js';
import axios from 'axios';

export default function EditPost({ initialPost }) {
    const route = useRoute();
    const [post, setPost] = useState(initialPost);

    const handleUpdate = async (e) => {
        e.preventDefault();

        await axios.put(
            route('posts.update', { post: post.id }),
            post
        );

        alert('Post updated successfully!');
    };

    const handleDelete = async () => {
        if (!confirm('Are you sure?')) return;

        await axios.delete(route('posts.destroy', { post: post.id }));
        window.location.href = route('posts.index');
    };

    return (
        <form onSubmit={handleUpdate}>
            <input
                type="text"
                value={post.title}
                onChange={(e) => setPost({ ...post, title: e.target.value })}
            />
            <textarea
                value={post.body}
                onChange={(e) => setPost({ ...post, body: e.target.value })}
            />
            <button type="submit">Update Post</button>
            <button type="button" onClick={handleDelete}>
                Delete Post
            </button>
        </form>
    );
}

Data Fetching

Using fetch API

import React, { useState, useEffect } from 'react';
import { useRoute } from 'ziggy-js';

export default function Post({ postId }) {
    const route = useRoute();
    const [post, setPost] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        async function fetchPost() {
            try {
                const response = await fetch(
                    route('api.posts.show', { post: postId })
                );
                const data = await response.json();
                setPost(data);
            } catch (err) {
                setError(err.message);
            } finally {
                setLoading(false);
            }
        }

        fetchPost();
    }, [postId]);

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error}</div>;

    return (
        <article>
            <h1>{post.title}</h1>
            <div>{post.body}</div>
        </article>
    );
}

Custom Hook for Data Fetching

import { useState, useEffect } from 'react';
import { useRoute } from 'ziggy-js';

function useLaravelRoute(routeName, params) {
    const route = useRoute();
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        async function fetchData() {
            try {
                const response = await fetch(route(routeName, params));
                const json = await response.json();
                setData(json);
            } catch (err) {
                setError(err);
            } finally {
                setLoading(false);
            }
        }

        fetchData();
    }, [routeName, JSON.stringify(params)]);

    return { data, loading, error };
}

// Usage
export default function Posts() {
    const { data: posts, loading, error } = useLaravelRoute('api.posts.index');

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error loading posts</div>;

    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    );
}

TypeScript Support

Basic TypeScript Usage

import React from 'react';
import { useRoute } from 'ziggy-js';
import type { RouteUrl } from 'ziggy-js';

interface Post {
    id: number;
    title: string;
    body: string;
}

interface PostCardProps {
    post: Post;
}

export default function PostCard({ post }: PostCardProps) {
    const route = useRoute();
    const postUrl: RouteUrl = route('posts.show', { post: post.id });

    return (
        <article>
            <h2>{post.title}</h2>
            <a href={postUrl}>Read more</a>
        </article>
    );
}

With Generated Types

First, generate Ziggy’s TypeScript definitions:
php artisan ziggy:generate --types
Now you get full autocomplete for route names and parameters:
import React from 'react';
import { useRoute } from 'ziggy-js';

export default function Navigation() {
    const route = useRoute();

    // TypeScript knows 'posts.show' requires a 'post' parameter
    const url = route('posts.show', { post: 1 }); // ✓ Valid
    
    // TypeScript will error on invalid route names
    // const invalid = route('invalid.route'); // ✗ Type error

    return <a href={url}>View Post</a>;
}

Strict Type Checking

Enable strict route name checking:
// ziggy.d.ts
declare module 'ziggy-js' {
    interface TypeConfig {
        strictRouteNames: true;
    }
}

export {};

Hook Implementation

The useRoute() hook is implemented as follows:
export function useRoute(defaultConfig) {
    if (
        !defaultConfig &&
        !globalThis.Ziggy &&
        typeof Ziggy === 'undefined' &&
        !document.getElementById('ziggy-routes-json')
    ) {
        throw new Error(
            'Ziggy error: missing configuration. Ensure that a `Ziggy` variable is defined globally or pass a config object into the useRoute hook.',
        );
    }

    return (name, params, absolute, config = defaultConfig) =>
        route(name, params, absolute, config);
}
The hook returns a version of route() that automatically uses the config you passed to useRoute(), or falls back to the global Ziggy variable.

Server-Side Rendering (SSR)

For SSR frameworks like Next.js, ensure Ziggy’s config is available on both server and client:
// pages/_app.js
import { Ziggy } from '../ziggy.js';

if (typeof window === 'undefined') {
    // Server-side
    globalThis.Ziggy = Ziggy;
} else {
    // Client-side
    window.Ziggy = Ziggy;
}

function MyApp({ Component, pageProps }) {
    return <Component {...pageProps} />;
}

export default MyApp;

Troubleshooting

”Missing configuration” error

This error means useRoute() can’t find Ziggy’s config. Either:
  1. Pass the config directly: useRoute(Ziggy)
  2. Make it global: globalThis.Ziggy = Ziggy
  3. Use the @routes Blade directive

Routes not updating after changes

If you’re using ziggy:generate, re-run the command after changing routes:
php artisan ziggy:generate

TypeScript errors with route names

Generate type definitions:
php artisan ziggy:generate --types

Next Steps

TypeScript

Set up full type safety and autocompletion

Vue

Use Ziggy with Vue instead

Route Helper

Learn all route() function capabilities

JavaScript Frameworks

General framework integration guide

Build docs developers (and LLMs) love