TinyKit Pro Docs

Modular Routing System

TinyKit Pro implements a centralized, modular route configuration system that separates routes by domain (core auth, public pages, admin panel, organizations...

TinyKit Pro implements a centralized, modular route configuration system that separates routes by domain (core auth, public pages, admin panel, organizations). This architecture enables clean separation of concerns and easy customization for forks.

Architecture Overview

The routing system is split into domain-specific configuration files in a centralized location (/src/config/routes/), making it easy to:

  • Update specific domains without affecting others (e.g., admin panel updates don't touch public routes)
  • Customize public routes in forks while preserving admin updates from upstream
  • Maintain clear boundaries between authentication, public, admin, and organization features

Core Components

  • Core Routes (/src/config/routes/core.ts): Authentication routes, rarely customized
  • Public Routes (/src/config/routes/public.ts): Public navigation and user dashboard, fork-customizable
  • Admin Routes (/src/config/routes/admin.ts): Admin panel routes, frequently updated upstream
  • Organization Routes (/src/config/routes/org.ts): Organization workspace routes
  • Dynamic Registry: Runtime registration system for menu items (admin module)
  • Feature Toggles: Environment variable-based feature control

File Structure

src/
├── config/
│   └── routes/
│       ├── index.ts                  # Main entry point, re-exports all routes
│       ├── core.ts                   # Auth routes (rarely customized)
│       ├── public.ts                 # Public navigation + home routes (fork-friendly)
│       ├── admin.ts                  # Admin panel routes + dynamic registry
│       └── org.ts                    # Organization workspace routes
├── features/
│   └── navigation/
│       └── components/               # Navigation components
│           ├── root/                 # Header, Footer (public)
│           ├── sidebars/             # Admin, User, Org sidebars
│           └── user-menu/            # User dropdown menu
└── app/                              # App Router pages only (no component files)

Route Configuration Files

Core Routes (core.ts)

Contains authentication routes that rarely need customization:

/**
 * Authentication Routes
 *
 * Core authentication routes - rarely customized in forks.
 * These routes handle sign-in, sign-up, password reset, etc.
 */
export const authRoutes = {
  signIn: {
    title: "Sign In",
    href: "/auth/sign-in",
  },
  signUp: {
    title: "Sign Up",
    href: "/auth/sign-up",
  },
  resetPassword: {
    title: "Reset Password",
    href: "/auth/reset-password",
  },
  // ... other auth routes
} as const;

Public Routes (public.ts)

Contains public navigation and home dashboard routes. Fork-friendly for customization:

/**
 * Public Navigation Routes
 *
 * Routes shown in the public header (logged out state).
 * Fork-customizable - change these to match your landing page structure.
 */
export const publicRoutes = {
  home: {
    title: "Home",
    href: "/",
  },
  pricing: {
    title: "Pricing",
    href: "/pricing",
  },
  // ... customize for your fork
} as const;

/**
 * User Dashboard Routes
 *
 * Routes for authenticated user home dashboard.
 */
export const homeRoutes = {
  dashboard: {
    title: "Home",
    href: "/home",
    icon: House,
  },
  account: {
    title: "Account",
    href: "/home/account",
    icon: User,
  },
  // ... other dashboard routes
} as const;

Admin Routes (admin.ts)

Contains admin panel routes and dynamic menu registration. Frequently updated upstream:

/**
 * Admin Panel Routes
 *
 * Structured by sections for organized admin navigation.
 * This file is frequently updated upstream - merge carefully in forks.
 */
export const adminRoutes = {
  dashboard: {
    title: "Dashboard",
    href: "/admin",
    icon: LayoutDashboard,
  },
  users: {
    title: "Users",
    href: "/admin/users",
    icon: Users,
  },
  // ... other admin routes organized by section
} as const;

// Dynamic Registry for Admin Menu Items
export const adminMenuRegistry = {
  items: [] as AdminMenuItem[],
};

export function registerAdminMenuItem(item: AdminMenuItem) {
  adminMenuRegistry.items.push(item);
}

// Auto-initialization for admin menu
export function initAdminModule() {
  registerAdminMenuItem({
    type: "route",
    title: "Admin Panel",
    href: "/admin",
    icon: Shield,
    allowedUserRoles: ["admin"],
  });
}

Organization Routes (org.ts)

Contains organization workspace routes with dynamic slug-based routing:

/**
 * Organization Workspace Routes
 *
 * Dynamic routes for organization workspaces using [orgSlug] parameter.
 */
export const orgRoutes = {
  dashboard: {
    title: "Dashboard",
    href: "/orgs/[orgSlug]",
    icon: LayoutDashboard,
  },
  settings: {
    title: "Settings",
    href: "/orgs/[orgSlug]/settings",
    icon: Settings,
  },
  // ... other org routes
} as const;

// Helper to build org-specific routes
export function buildOrgRoute(
  routeKey: keyof typeof orgRoutes,
  orgSlug: string,
): string {
  return orgRoutes[routeKey].href.replace("[orgSlug]", orgSlug);
}

Centralized Configuration Benefits

Fork Management

The centralized structure makes fork management easier:

  1. Public Routes: Safe to customize heavily in forks (your landing pages, your structure)
  2. Admin Routes: Merge upstream updates carefully (admin features frequently added)
  3. Core Routes: Rarely changes upstream (auth is stable)
  4. Org Routes: Can customize per-fork if needed
// In admin.ts
export const adminMenuRegistry = {
  items: [] as AdminMenuItem[],
};

export function registerAdminMenuItem(item: AdminMenuItem) {
  adminMenuRegistry.items.push(item);
}

// Auto-initialize when routes module is imported
export function initAdminModule() {
  registerAdminMenuItem({
    type: "route",
    title: "Admin Panel",
    href: "/admin",
    icon: Shield,
    allowedUserRoles: ["admin"],
  });
}

The registry is auto-initialized on the client side when the routes module is imported:

// In index.ts
if (typeof window !== "undefined") {
  void import("./admin").then((mod) => {
    mod.initAdminModule();
  });
}

User Menu Integration

The user menu in the header dropdown uses routes from the centralized configuration:

// In user-menu.config.ts
import { homeRoutes, adminRoutes } from "@/config/routes";

export const getUserMenuConfig = (userRole?: string) => {
  const menuItems = [homeRoutes.dashboard, homeRoutes.account];

  // Add admin panel for authorized users
  if (userRole === "admin") {
    menuItems.push(adminRoutes.dashboard);
  }

  return menuItems;
};

Sidebars (Admin, User, Org) import routes from the centralized configuration:

// In AdminSidebar.tsx
import { adminRoutes, sectionConfig } from "@/config/routes";

export function AdminSidebar() {
  return (
    <aside>
      {Object.entries(sectionConfig).map(([sectionKey, section]) => (
        <div key={sectionKey}>
          <h3>{section.title}</h3>
          {section.routes.map((route) => (
            <Link href={route.href}>{route.title}</Link>
          ))}
        </div>
      ))}
    </aside>
  );
}

Feature Toggles

Features can be conditionally enabled using database-driven feature settings, controlled via the admin panel (Admin > Site Settings > General).

Using Feature Settings

Feature settings are accessed via the useFeatureSettings() hook:

import { useFeatureSettings } from "@/lib/features";

// In components
function OrgSelector() {
  const { enableOrganizations, isLoading } = useFeatureSettings();

  if (isLoading) return null;
  if (!enableOrganizations) return null;

  return <OrganizationDropdown />;
}

Benefits of Database-Driven Feature Toggles

  • Runtime Control: Toggle features without deployment or restart
  • Admin UI: Configure via Admin > Site Settings > General
  • Real-time Updates: Changes propagate instantly to all connected clients
  • Testability: Easy to toggle in tests without environment setup

Role-Based Access Control

Routes can include role restrictions that are enforced in navigation components:

// In admin.ts
export const adminRoutes = {
  dashboard: {
    title: "Dashboard",
    href: "/admin",
    icon: LayoutDashboard,
    allowedUserRoles: ["admin"],
  },
  users: {
    title: "Users",
    href: "/admin/users",
    icon: Users,
    allowedUserRoles: ["admin"],
  },
} as const;

// Helper to filter routes by role
export function getFilteredAdminRoutes(userRole?: string) {
  return Object.values(adminRoutes).filter((route) => {
    if (!route.allowedUserRoles) return true;
    return userRole && route.allowedUserRoles.includes(userRole);
  });
}

Page-Level Protection

Backend protection is always enforced via Convex access control:

// In Convex backend
export const getAdminData = query({
  args: {},
  handler: async (ctx) => {
    const { userId } = await requireAccess(ctx, {
      userRole: ["admin"],
    });
    // User is authorized
    return await ctx.db.query("adminData").collect();
  },
});

Authentication-Aware Navigation

Navigation components use Better Auth session state for authentication:

// In HeaderNav.tsx
import { publicRoutes, authRoutes } from "@/config/routes";

export function HeaderNav() {
  const session = useSession();

  return (
    <nav>
      {/* Public navigation */}
      {Object.values(publicRoutes).map((route) => (
        <Link key={route.href} href={route.href}>
          {route.title}
        </Link>
      ))}

      {/* Auth state UI */}
      {!session ? (
        <>
          <Link href={authRoutes.signIn.href}>{authRoutes.signIn.title}</Link>
          <Link href={authRoutes.signUp.href}>{authRoutes.signUp.title}</Link>
        </>
      ) : (
        <UserMenu />
      )}
    </nav>
  );
}

Customization Guide

Customizing Public Routes (Fork-Friendly)

The public.ts file is designed for fork customization:

// In src/config/routes/public.ts
export const publicRoutes = {
  home: { title: "Home", href: "/" },
  features: { title: "Features", href: "/features" }, // Add your routes
  yourCustomPage: { title: "Custom", href: "/custom" }, // Fork-specific
  pricing: { title: "Pricing", href: "/pricing" },
} as const;

Customize without worrying about upstream conflicts - this file is stable.

Adding Admin Routes (Merge Carefully)

The admin.ts file receives frequent upstream updates. When adding routes:

  1. Add to appropriate section in sectionConfig
  2. Use descriptive route keys
  3. Document custom routes for merge resolution
// In src/config/routes/admin.ts - Custom section
export const sectionConfig = {
  // ... existing sections
  custom: {
    title: "Custom Features",
    routes: [
      {
        key: "customFeature",
        title: "My Custom Feature",
        href: "/admin/custom-feature",
        icon: Sparkles,
      },
    ],
  },
} as const;

Extending Organization Routes

Add organization-specific routes in org.ts:

// In src/config/routes/org.ts
export const orgRoutes = {
  // ... existing routes
  customOrgFeature: {
    title: "Custom Feature",
    href: "/orgs/[orgSlug]/custom",
    icon: CustomIcon,
  },
} as const;

// Use the helper to build URLs
const url = buildOrgRoute("customOrgFeature", "my-org");
// Returns: "/orgs/my-org/custom"

Best Practices

Route Organization

  • Domain Separation: Keep auth, public, admin, and org routes in separate files
  • Type Safety: Use as const for route objects to enable strict typing
  • Helper Functions: Use buildOrgRoute() for dynamic slug-based routes
  • Role Restrictions: Include allowedUserRoles arrays where needed

Import Patterns

// ✅ Good: Import from centralized index
import { publicRoutes, adminRoutes, authRoutes } from "@/config/routes";

// ❌ Bad: Import from individual files
import { publicRoutes } from "@/config/routes/public";
import { adminRoutes } from "@/config/routes/admin";

Fork Management

  • Public Routes: Customize freely - designed for forks
  • Admin Routes: Merge carefully - frequent upstream updates
  • Core Routes: Rarely customize - stable upstream
  • Org Routes: Fork-specific customization safe
  • Centralized Imports: Always import routes from @/config/routes
  • Role Filtering: Use helper functions like getFilteredAdminRoutes()
  • Type Safety: Leverage TypeScript for route autocomplete

Migration Guide

From App Directory Routes to Centralized Config

The new structure centralizes routes in /src/config/routes/ instead of scattering them in app/_nav/ directories.

Migration steps:

  1. Move route definitions from app/*/_nav/routes.config.ts to appropriate config/routes/*.ts file
  2. Update imports in navigation components to use @/config/routes
  3. Remove old _nav directories from app/ after migration
  4. Update component files - move any non-page components from app/ to features/

Example migration:

// Before: app/admin/_nav/routes.config.ts
export const adminRoutes = {
  dashboard: { title: "Dashboard", href: "/admin" },
};

// After: src/config/routes/admin.ts (centralized)
export const adminRoutes = {
  dashboard: { title: "Dashboard", href: "/admin", icon: LayoutDashboard },
} as const;

Troubleshooting

Common Issues

Import errors after refactor:

  • Verify imports use @/config/routes not old @/app/*/_nav/routes.config
  • Check that centralized route files export all needed routes

Routes not appearing in navigation:

  • Verify role-based filtering in getFilteredAdminRoutes()
  • Check that user has correct userRole for restricted routes
  • Ensure routes are properly defined with required fields

TypeScript errors:

  • Use as const on route objects for proper type inference
  • Import types from @/config/routes for route definitions

Debug Tips

// In browser console - check available routes
import { adminRoutes, publicRoutes } from "@/config/routes";
console.log({ adminRoutes, publicRoutes });

// Check user role
const currentUser = useQuery(api.users.private.queries.getMe);
console.log("User role:", currentUser?.userRole);

The centralized routing system provides clear domain separation, easier fork management, and better merge strategies for upstream updates while maintaining excellent type safety and developer experience.

On this page

Ship your startup faster. In minutes.

Get TinyKit Pro