Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tighten/ziggy/llms.txt

Use this file to discover all available pages before exploring further.

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