How Resource-Based Routes Work in refine-shadcn: From Declaration to Navigation

Resource-based routes in refine-shadcn connect declarative resource definitions to dynamic URLs through a centralized resources array, enabling automatic navigation generation, permission-aware routing, and reusable CRUD components across your React application.

The ferdiunal/refine-shadcn library implements a declarative routing system where resource-based routes drive the entire UI navigation. Instead of hardcoding paths throughout your application, you define entities once in a static configuration file, and the framework automatically generates URLs, sidebar links, and access-controlled actions. This architecture allows you to add new entities like "comments" or "users" by simply updating a single array, with all navigation components adapting instantly.

Declaring Resources in src/resources.tsx

All resource-based routes originate from a single source of truth. In templates/vite-react/src/resources.tsx, you export an array of ResourceProps objects that declare each entity's name, base path, and available CRUD operations.

// templates/vite-react/src/resources.tsx
export const resources: ResourceProps[] = [
    {
        name: "dashboard",
        list: "/dashboard",
        meta: { title: "Dashboard", icon: <Home .../> },
    },
    {
        name: "posts",
        list: "/posts",
        show: "/posts/show/:id",
        create: "/posts/create",
        edit: "/posts/edit/:id",
        meta: { title: "Posts", icon: <NewspaperIcon .../> },
    },
];

Each resource object specifies the list, show, create, and edit route patterns. The meta field stores UI-specific data like titles and icons. When you add a new resource here, the entire application instantly recognizes the new routes without requiring changes to navigation components or CRUD pages.

Accessing the Resource Context with useResource

The @refinedev/core package automatically registers your resources array in a global ResourceProvider. Components throughout the application access this data via the useResource hook, as implemented in packages/theme/src/layouts/default.tsx.

// packages/theme/src/layouts/default.tsx
import { useResource } from "@refinedev/core";

const DefaultLayout = () => {
    const { resources } = useResource();
    const firstDashboard = resources?.[0]; // Used for logo link
    
    return (
        // Layout renders Sidebar with full resources list
    );
};

This hook returns the complete resources array along with helper methods to retrieve specific entries by name. By consuming this context, the DefaultLayout dynamically sets the logo link to the first resource (typically "dashboard") and passes the resource configuration down to child components.

Generating URLs with useGetShowUrl and useGetEditUrl

Concrete URL generation happens through specialized hooks that combine routing logic with access control. The useGetShowUrl hook in packages/theme/src/hooks/useGetShowUrl.tsx demonstrates this pattern.

// packages/theme/src/hooks/useGetShowUrl.tsx
const { showUrl: generateShowUrl } = useNavigation();
const { id, resource: _resource } = useResource(resource);
const { data } = useCan({ 
    resource, 
    action: "show", 
    params: { id: recordItemId, resource: _resource } 
});

const showUrl = resource && (recordItemId || id) 
    ? generateShowUrl(resource, recordItemId ?? id, meta) 
    : "";

This hook performs two critical functions. First, it calls useNavigation() to generate the base path while substituting the :id placeholder with the actual record identifier. Second, it invokes useCan to verify the user has permission for the "show" action, returning a can boolean and localized reason if access is denied. The companion useGetEditUrl hook in packages/theme/src/hooks/useGetEditUrl.tsx implements identical logic for edit routes.

Building Dynamic Navigation in Sidebar.tsx

The sidebar component in packages/theme/src/components/sidebar.tsx iterates over the resource definitions to construct navigation links automatically. It generates possible route patterns for each resource to determine active states.

// packages/theme/src/components/sidebar.tsx
const paths = [
    item.list?.toString(),
    item.create?.toString(),
    item.edit?.toString()?.replace(":id", resourceParams.id as string),
    item.show?.toString()?.replace(":id", resourceParams.id as string),
].filter(Boolean);

For each menu item derived from the resources array, the component builds an array of potential paths by resolving dynamic segments like :id against current URL parameters. When the current pathname matches any entry in this array, the sidebar marks that navigation item as active. The component also handles collapsed states, wrapping items in tooltips when isCollapsed is true.

Implementing Resource-Agnostic CRUD Actions

Table components leverage resource-based routes to render action buttons without hardcoding URLs. In templates/vite-react/src/pages/posts/list.tsx, the Table component receives a resource prop that drives all navigation.

// templates/vite-react/src/pages/posts/list.tsx
<Table.ShowAction
    title="Detail"
    row={original}
    resource="posts"
    icon={<Eye size={16} />}
/>
<Table.EditAction
    title="Edit"
    row={original}
    resource="posts"
    icon={<Edit size={16} />}
/>

Because these actions receive the resource name "posts", they internally call useGetShowUrl and useGetEditUrl to resolve /posts/show/:id and /posts/edit/:id respectively. This design makes the Table component completely reusable—simply change the resource prop to "comments" or "users", and all URLs update automatically while maintaining proper access control checks.

Adding a New Resource Without Code Changes

The true power of resource-based routes emerges when extending your application. To add a "comments" entity, you only modify templates/vite-react/src/resources.tsx:

{
    name: "comments",
    list: "/comments",
    show: "/comments/show/:id",
    create: "/comments/create",
    edit: "/comments/edit/:id",
    meta: { title: "Comments", icon: <MessageSquare .../> },
}

Upon saving, the sidebar instantly displays a "Comments" navigation item, the layout includes it in breadcrumb calculations (handled in packages/theme/src/components/breadcrumbs.tsx), and all CRUD tables can reference resource="comments" without touching any routing logic.

Summary

  • Centralized configuration: Define all routes in templates/vite-react/src/resources.tsx using the ResourceProps interface.
  • Global access: Retrieve resource data anywhere via useResource() from @refinedev/core.
  • Dynamic URL generation: Use useGetShowUrl and useGetEditUrl to convert resource names and record IDs into accessible URLs with built-in permission checking.
  • Automatic navigation: The Sidebar component in packages/theme/src/components/sidebar.tsx builds links and active states directly from the resources array.
  • Reusable CRUD: Table actions accept a resource prop to generate correct routes for any entity without hardcoded paths.

Frequently Asked Questions

How do I add access control to resource-based routes?

The useGetShowUrl and useGetEditUrl hooks automatically integrate with useCan from @refinedev/core to check permissions before generating URLs. When you call these hooks with a resource name and record ID, they return a can boolean indicating whether the user has rights to perform the action, along with a localized reason string if access is denied. Implement these hooks in your components to conditionally render action buttons or redirect unauthorized users.

Can I use resource-based routes outside of the standard CRUD pages?

Yes. Any component can import useGetEditUrl from @ferdiunal/refine-shadcn to generate URLs for custom implementations. For example, in a custom edit page, you can resolve the edit URL for a "comments" resource by calling the hook with the resource name and ID, then use the returned URL in a form action or router link while respecting the permission system.

Where does refine-shadcn store the active resource list?

The framework stores resources in a ResourceProvider managed by @refinedev/core, which reads your resources array from templates/vite-react/src/resources.tsx when the application initializes. Components like DefaultLayout in packages/theme/src/layouts/default.tsx access this context via useResource() to render navigation and layout elements dynamically.

How does the sidebar determine which navigation item is active?

The Sidebar component in packages/theme/src/components/sidebar.tsx generates an array of possible route patterns for each resource—including list, create, edit, and show paths—by substituting :id placeholders with current URL parameters. It compares these generated paths against the current browser location using pattern matching, marking the corresponding menu item as active when a match occurs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →