Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问许可证需确认审计异常

routing-patterns路由模式

Agent Skill

routing-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

225

周安装

9

GitHub Stars

34

下载量

73
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:routing-patterns(路由模式)
来源仓库:https://github.com/kinfe123/fm-skills
仓库路径:skills/routing-patterns
安装命令:
npx skills add https://github.com/kinfe123/fm-skills --skill routing-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/kinfe123/fm-skills --skill routing-patterns

简介

routing-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词快速定位候选结果时使用。

  • 它适用于研究检索类任务,可结合来源仓库和原始 README 进一步核验具体用法。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装并使用该技能。
  • 安装前建议确认权限范围、维护状态,以及是否会触发联网或文件读写操作。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Routing Patterns

Overview

Routing determines how URLs map to content and how navigation between pages works. The approach differs significantly between traditional server-rendered apps and modern SPAs.

Server-Side Routing

Server determines what to render based on the URL.

How It Works

1. User navigates to /about
2. Browser sends request to server
3. Server matches /about to handler
4. Server renders HTML
5. Server sends complete HTML response
6. Browser loads new page (full reload)

Characteristics

Request: GET /products/123

Server:
  Route table:
    /            → HomeController
    /about       → AboutController
    /products/:id → ProductController  ← matches

  ProductController:
    - Fetches product 123
    - Renders HTML
    - Returns response

Pros and Cons

ProsCons
SEO-friendly (full HTML)Full page reload
Simple mental modelSlower navigation
Works without JavaScriptState lost between pages
Direct URL to content mappingServer must handle every route

Client-Side Routing

JavaScript handles routing in the browser, no server round-trip for navigation.

How It Works

1. Initial: Browser loads app shell + JS
2. User clicks link
3. JavaScript intercepts click (preventDefault)
4. JS updates URL using History API
5. JS renders new view/component
6. No server request for HTML

The History API

// Push new URL to history (no page reload)
history.pushState({ page: 'about' }, '', '/about');

// Replace current URL
history.replaceState({ page: 'home' }, '', '/');

// Listen for back/forward navigation
window.addEventListener('popstate', (event) => {
  // Render based on current URL
  renderRoute(window.location.pathname);
});

Basic Router Implementation

// Simplified client-side router concept
class Router {
  constructor() {
    this.routes = {};
    window.addEventListener('popstate', () => this.handleRoute());
  }

  register(path, component) {
    this.routes[path] = component;
  }

  navigate(path) {
    history.pushState({}, '', path);
    this.handleRoute();
  }

  handleRoute() {
    const path = window.location.pathname;
    const component = this.routes[path] || this.routes['/404'];
    component.render();
  }
}

// Usage
router.register('/about', AboutComponent);
router.register('/products/:id', ProductComponent);

Link Interception

// Intercept link clicks for client-side navigation
document.addEventListener('click', (e) => {
  const link = e.target.closest('a');
  if (link && link.href.startsWith(window.location.origin)) {
    e.preventDefault();
    router.navigate(link.pathname);
  }
});

File-Based Routing

Route structure derived from file system layout.

Common Patterns

File System                          URL
──────────────────────────────       ─────────────
pages/index.tsx                  →   /
pages/about.tsx                  →   /about
pages/blog/index.tsx             →   /blog
pages/blog/[slug].tsx            →   /blog/:slug
pages/docs/[...path].tsx         →   /docs/*
pages/[category]/[id].tsx        →   /:category/:id

Framework Implementations

Next.js (App Router):

app/
├── page.tsx                    → /
├── about/page.tsx              → /about
├── blog/[slug]/page.tsx        → /blog/:slug
└── (marketing)/                → Route group (no URL segment)
    └── pricing/page.tsx        → /pricing

SvelteKit:

src/routes/
├── +page.svelte                → /
├── about/+page.svelte          → /about
├── blog/[slug]/+page.svelte    → /blog/:slug
└── [[optional]]/+page.svelte   → / or /:optional

Remix:

app/routes/
├── _index.tsx                  → /
├── about.tsx                   → /about
├── blog._index.tsx             → /blog
├── blog.$slug.tsx              → /blog/:slug
└── $.tsx                       → Catch-all (splat)

Dynamic Routes

Parameter Types

Static:     /about          → Exact match
Dynamic:    /blog/[slug]    → Single parameter
Catch-all:  /docs/[...path] → Multiple segments
Optional:   /[[locale]]/    → With or without parameter

Parameter Access

Concept (framework-agnostic):

// URL: /products/shoes/nike-air-max

// Route: /products/[category]/[id]
// Parameters: { category: 'shoes', id: 'nike-air-max' }

// Route: /products/[...path]
// Parameters: { path: ['shoes', 'nike-air-max'] }

Route Groups and Layouts

Nested Layouts

app/
├── layout.tsx              → Root layout (applies to all)
├── (shop)/
│   ├── layout.tsx          → Shop layout (header, cart)
│   ├── products/page.tsx   → /products (uses shop layout)
│   └── cart/page.tsx       → /cart (uses shop layout)
└── (marketing)/
    ├── layout.tsx          → Marketing layout (different nav)
    ├── about/page.tsx      → /about (uses marketing layout)
    └── pricing/page.tsx    → /pricing (uses marketing layout)

Parallel Routes

Load multiple views simultaneously:

app/
├── @sidebar/
│   └── page.tsx            → Sidebar content
├── @main/
│   └── page.tsx            → Main content
└── layout.tsx              → Renders both in parallel

Intercepting Routes

Handle routes differently in different contexts:

app/
├── photos/[id]/page.tsx        → Full page view
├── @modal/(.)photos/[id]/       → Modal overlay (when navigating internally)
│   └── page.tsx
└── feed/page.tsx                → Grid with links to photos

Navigation Patterns

Declarative Navigation

// React Router / Next.js
<Link href="/about">About</Link>

// Vue Router
<router-link to="/about">About</router-link>

// SvelteKit
<a href="/about">About</a>  <!-- Enhanced automatically -->

Programmatic Navigation

// React Router
const navigate = useNavigate();
navigate('/dashboard');

// Next.js
const router = useRouter();
router.push('/dashboard');

// Vue Router
router.push('/dashboard');

// SvelteKit
import { goto } from '$app/navigation';
goto('/dashboard');

Navigation Options

// Replace history (no back button)
router.replace('/login');

// With query parameters
router.push('/search?q=term');

// With hash
router.push('/docs#section');

// Scroll to top
router.push('/page', { scroll: true });

// Shallow routing (no data refetch)
router.push('/page', { shallow: true });

Route Protection

Authentication Guards

// Middleware pattern (Next.js)
export function middleware(request) {
  const token = request.cookies.get('auth-token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

// Route-level check
export async function loader({ request }) {
  const user = await getUser(request);
  if (!user) {
    throw redirect('/login');
  }
  return { user };
}

Authorization Patterns

// Role-based routing
if (!user.roles.includes('admin')) {
  redirect('/unauthorized');
}

// Feature flags
if (!features.newDashboard) {
  redirect('/legacy-dashboard');
}

URL State Management

Query Parameters

// Read query params
const searchParams = useSearchParams();
const query = searchParams.get('q');

// Update query params
const params = new URLSearchParams(searchParams);
params.set('page', '2');
router.push(`/products?${params.toString()}`);

Hash-Based State

// URL: /docs#installation
// Scroll to section
useEffect(() => {
  const hash = window.location.hash;
  if (hash) {
    document.querySelector(hash)?.scrollIntoView();
  }
}, []);

State in URL vs Component

URL State (shareable, bookmarkable):
  - Current page, filters, search query
  - Tab selection
  - Modal open state (sometimes)

Component State (ephemeral):
  - Form input before submission
  - Hover/focus states
  - Temporary UI state

Performance Patterns

Prefetching

// Next.js - automatic on hover
<Link href="/about" prefetch={true}>About</Link>

// Manual prefetch
router.prefetch('/dashboard');

Code Splitting by Route

// Lazy load route components
const Dashboard = lazy(() => import('./Dashboard'));

// Routes load only when accessed
<Route path="/dashboard" element={
  <Suspense fallback={<Loading />}>
    <Dashboard />
  </Suspense>
} />

Route Transitions

// Loading states during navigation
const navigation = useNavigation();

{navigation.state === 'loading' && <LoadingBar />}

Common Routing Pitfalls

1. Breaking the Back Button

// Bad: Replaces every navigation
router.replace('/page');  // Can't go back

// Good: Push for normal navigation
router.push('/page');  // Back button works

2. Not Handling 404s

// Ensure catch-all route exists
// app/[...not-found]/page.tsx or pages/404.tsx

3. Client-Only Routes in SSR

// Bad: useSearchParams in server component
// Causes hydration mismatch

// Good: Use in client component or read in middleware

4. Hash URLs for SEO Content

// Bad: /#/products/123 (not indexable)
// Good: /products/123 (indexable)

Deep Dive: Understanding Routing From First Principles

What is a URL? Anatomy of Web Addresses

Before understanding routing, you must understand URLs (Uniform Resource Locators):

https://www.example.com:443/products/shoes?color=red&size=10#reviews
│       │              │   │              │                    │
│       │              │   │              │                    └─ Fragment (client-only, not sent to server)
│       │              │   │              │
│       │              │   │              └─ Query String (key-value pairs)
│       │              │   │
│       │              │   └─ Pathname (the route)
│       │              │
│       │              └─ Port (usually implicit: 80 for http, 443 for https)
│       │
│       └─ Hostname (domain)
│
└─ Protocol (scheme)

Key insight: Only protocol, hostname, port, pathname, and query are sent to the server. The fragment (#hash) stays in the browser.

The History of Web Routing

Era 1: File-Based (1990s)

URL: /products/shoes.html
Server: Find file at /var/www/products/shoes.html, return it

URLs literally mapped to files on disk.

Era 2: Dynamic Routing (2000s)

URL: /products.php?id=123
Server: Execute products.php, which reads $_GET['id'] = 123

Server-side scripts processed parameters.

Era 3: RESTful Routes (2010s)

URL: /products/123
Server: Route pattern /products/:id matches, extract id=123

Clean URLs with pattern matching.

Era 4: Client-Side Routing (2010s-present)

URL: /products/123
Client: JavaScript intercepts, renders Product component with id=123
Server: May never see this request (after initial load)

How Server-Side Routing Actually Works

When a server receives a request, it must decide what to do:

// Express.js example - simplified internal logic
class Router {
  constructor() {
    this.routes = [];
  }

  // Register route
  get(pattern, handler) {
    this.routes.push({
      method: 'GET',
      pattern: this.parsePattern(pattern),  // /users/:id → regex
      handler
    });
  }

  // Pattern to regex conversion
  parsePattern(pattern) {
    // /users/:id → /^\/users\/([^\/]+)$/
    // /posts/:id/comments → /^\/posts\/([^\/]+)\/comments$/
    const regexStr = pattern
      .replace(/:([^/]+)/g, '([^/]+)')  // :param → capture group
      .replace(/\//g, '\\/');            // Escape slashes
    return new RegExp(`^${regexStr}$`);
  }

  // Handle incoming request
  handle(req, res) {
    const { method, url } = req;
    const pathname = new URL(url, 'http://localhost').pathname;

    // Find matching route
    for (const route of this.routes) {
      if (route.method !== method) continue;

      const match = pathname.match(route.pattern);
      if (match) {
        // Extract params
        req.params = this.extractParams(route.pattern, match);
        return route.handler(req, res);
      }
    }

    // No match - 404
    res.status(404).send('Not Found');
  }
}

Route matching order matters:

// These are checked in order
app.get('/users/new', newUserHandler);     // Must be BEFORE :id
app.get('/users/:id', getUserHandler);     // Would match "new" as id otherwise

// Request: GET /users/new
// Check 1: /users/new matches! → newUserHandler
// Never reaches /users/:id

// Request: GET /users/123
// Check 1: /users/new doesn't match
// Check 2: /users/:id matches! → getUserHandler with id=123

How Client-Side Routing Intercepts Browser Behavior

The browser has default navigation behavior. Client-side routing overrides it:

// DEFAULT BROWSER BEHAVIOR:
// 1. User clicks <a href="/about">
// 2. Browser sees href attribute
// 3. Browser sends GET request to /about
// 4. Browser receives HTML response
// 5. Browser replaces entire page

// SPA INTERCEPTION:
document.addEventListener('click', (event) => {
  // Find the link element (might be nested: <a><span>Click</span></a>)
  const anchor = event.target.closest('a');
  if (!anchor) return;  // Not a link click

  // Check if it's an internal link
  const url = new URL(anchor.href);
  if (url.origin !== window.location.origin) return;  // External link

  // Check for special cases
  if (anchor.hasAttribute('download')) return;  // Download link
  if (anchor.target === '_blank') return;       // New tab
  if (event.metaKey || event.ctrlKey) return;   // Cmd/Ctrl+click

  // NOW we intercept
  event.preventDefault();  // STOP browser's default behavior

  // Handle navigation ourselves
  navigateTo(url.pathname + url.search + url.hash);
});

function navigateTo(path) {
  // Update URL bar without page reload
  window.history.pushState({ path }, '', path);

  // Render the appropriate component
  renderRoute(path);
}

The History API in Detail

The History API is what makes SPAs possible without hash URLs:

// The history stack is like browser tabs' back/forward buttons
// But for a single tab:

// Initial state
// History: [ {url: '/'} ]
//                  ↑ current

// User navigates to /about
history.pushState({ page: 'about' }, '', '/about');
// History: [ {url: '/'}, {url: '/about'} ]
//                              ↑ current

// User navigates to /contact
history.pushState({ page: 'contact' }, '', '/contact');
// History: [ {url: '/'}, {url: '/about'}, {url: '/contact'} ]
//                                              ↑ current

// User clicks back button
// Browser fires 'popstate' event
// History: [ {url: '/'}, {url: '/about'}, {url: '/contact'} ]
//                              ↑ current (moved back)

// User clicks forward button
// Browser fires 'popstate' event again
// History: [ {url: '/'}, {url: '/about'}, {url: '/contact'} ]
//                                              ↑ current (moved forward)

// IMPORTANT: pushState does NOT fire popstate
// Only back/forward buttons fire popstate
// Your router must handle both!

Handling popstate:

window.addEventListener('popstate', (event) => {
  // event.state contains the state object from pushState
  // If user used back button, this fires with previous state

  // The URL has ALREADY changed when this fires
  const currentPath = window.location.pathname;

  renderRoute(currentPath);
});

Hash Routing vs History Routing

Before the History API (HTML5, ~2010), SPAs used hash-based routing:

// HASH ROUTING:
// URLs look like: /#/about, /#/products/123

// Why hashes? The hash fragment is NEVER sent to server
// So changing it doesn't trigger page reload

// Navigation:
window.location.hash = '#/about';

// Listening for changes:
window.addEventListener('hashchange', () => {
  const route = window.location.hash.slice(1);  // Remove #
  renderRoute(route);
});

// PROBLEMS WITH HASH ROUTING:
// 1. SEO: Crawlers ignore hash fragments
//    /products/shoes and /products/shoes#/details are same URL to Google
//
// 2. Ugly URLs: example.com/#/products/shoes vs example.com/products/shoes
//
// 3. Server can't respond to routes: everything goes to root

History routing advantages:

// HISTORY ROUTING:
// URLs look like: /about, /products/123 (clean!)

// Navigation:
history.pushState({}, '', '/about');

// Server sees the real URL
// SEO works properly
// Clean, professional URLs

// REQUIREMENT: Server must handle all routes
// Server must return the SPA for ANY route
// Otherwise: /products/123 → 404 if user refreshes

File-Based Routing: How It Works Internally

Meta-frameworks use file system as routing configuration:

// At build time, the framework scans your files:

// File structure:
// app/
// ├── page.tsx          → /
// ├── about/page.tsx    → /about
// └── blog/[slug]/page.tsx → /blog/:slug

// Framework generates route table:
const routes = [
  { pattern: /^\/$/, component: () => import('./app/page.tsx') },
  { pattern: /^\/about$/, component: () => import('./app/about/page.tsx') },
  { pattern: /^\/blog\/([^/]+)$/, component: () => import('./app/blog/[slug]/page.tsx') },
];

// Dynamic segments become route parameters:
// [slug] → :slug → captured as params.slug
// [id] → :id → captured as params.id
// [...path] → * → captured as params.path (array)

Why file-based routing became popular:

EXPLICIT ROUTING (React Router, Express):
- Route definitions separate from components
- Easy to have mismatched routes/files
- Must manually update both

// routes.tsx
<Route path="/products/:id" component={ProductPage} />

// ProductPage.tsx (might be anywhere)
export function ProductPage() { ... }

FILE-BASED ROUTING:
- Location IS the route
- Impossible to have orphan components
- Adding page = adding route automatically

// app/products/[id]/page.tsx (location defines route)
export default function ProductPage() { ... }

Route Parameters: Extraction and Typing

Understanding how parameters flow from URL to code:

// URL: /products/shoes/nike-air-max/reviews

// Route pattern: /products/[category]/[productId]/reviews
// Regex: /^\/products\/([^/]+)\/([^/]+)\/reviews$/

// Matching process:
const match = url.match(pattern);
// match = [
//   '/products/shoes/nike-air-max/reviews',  // Full match
//   'shoes',                                   // Group 1: category
//   'nike-air-max'                            // Group 2: productId
// ]

// Framework extracts to params object:
const params = {
  category: 'shoes',
  productId: 'nike-air-max'
};

// Your component receives these:
function ProductReviewsPage({ params }) {
  console.log(params.category);   // 'shoes'
  console.log(params.productId);  // 'nike-air-max'
}

Catch-all routes:

// Route: /docs/[...path]
// URL: /docs/getting-started/installation/windows

// The [...path] captures EVERYTHING after /docs/
const params = {
  path: ['getting-started', 'installation', 'windows']  // Array!
};

// Useful for:
// - Documentation with arbitrary depth
// - File browsers
// - Fallback/404 routes

Nested Routes and Layouts: The Component Tree

Nested routing creates component hierarchies:

URL: /dashboard/settings/profile

Route hierarchy:
app/
├── layout.tsx           ← Root layout (nav, footer)
└── dashboard/
    ├── layout.tsx       ← Dashboard layout (sidebar)
    └── settings/
        ├── layout.tsx   ← Settings layout (tabs)
        └── profile/
            └── page.tsx ← Profile content

Renders as:
<RootLayout>              {/* Always present */}
  <DashboardLayout>       {/* Present for /dashboard/* */}
    <SettingsLayout>      {/* Present for /dashboard/settings/* */}
      <ProfilePage />     {/* The actual content */}
    </SettingsLayout>
  </DashboardLayout>
</RootLayout>

Why nested layouts matter:

// WITHOUT nested layouts:
// Every page must include all wrappers

function DashboardSettingsProfilePage() {
  return (
    <RootLayout>
      <DashboardSidebar />
      <SettingsTabs />
      <ProfileForm />   {/* Actual unique content */}
    </RootLayout>
  );
}

function DashboardSettingsSecurityPage() {
  return (
    <RootLayout>           {/* Duplicated */}
      <DashboardSidebar /> {/* Duplicated */}
      <SettingsTabs />     {/* Duplicated */}
      <SecurityForm />     {/* Actual unique content */}
    </RootLayout>
  );
}

// WITH nested layouts:
// Layouts persist, only inner content changes

// Navigating from /dashboard/settings/profile
// to /dashboard/settings/security:
// - RootLayout: PRESERVED (no re-render)
// - DashboardLayout: PRESERVED (no re-render)
// - SettingsLayout: PRESERVED (no re-render)
// - Only ProfilePage → SecurityPage changes!

Route Guards and Middleware: The Request Pipeline

Routes often need protection or preprocessing:

// THE MIDDLEWARE CHAIN:
// Request flows through layers before reaching handler

// Request: GET /dashboard/admin

// Layer 1: Logging middleware
function loggingMiddleware(request, next) {
  console.log(`${request.method} ${request.url}`);
  return next(request);  // Continue to next middleware
}

// Layer 2: Authentication middleware
function authMiddleware(request, next) {
  const token = request.cookies.get('auth-token');
  if (!token) {
    return redirect('/login');  // Short-circuit: stop here
  }
  request.user = decodeToken(token);
  return next(request);  // Continue to next middleware
}

// Layer 3: Authorization middleware
function adminMiddleware(request, next) {
  if (!request.user.isAdmin) {
    return redirect('/unauthorized');
  }
  return next(request);
}

// Layer 4: Route handler
function adminDashboardHandler(request) {
  return renderPage(<AdminDashboard user={request.user} />);
}

// Request flows:
// loggingMiddleware → authMiddleware → adminMiddleware → adminDashboardHandler
//                           ↓
//                    (if no token)
//                           ↓
//                    redirect('/login')

Prefetching: How Routers Optimize Navigation

Modern routers predict user navigation:

// HOVER PREFETCHING:
// When user hovers over a link, likely to click

<Link href="/about" prefetch>About</Link>

// Framework behavior:
link.addEventListener('mouseenter', () => {
  // Start loading the /about route code and data
  router.prefetch('/about');
});

// VIEWPORT PREFETCHING:
// Prefetch links visible on screen

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const href = entry.target.getAttribute('href');
      router.prefetch(href);
    }
  });
});

document.querySelectorAll('a[prefetch]').forEach((link) => {
  observer.observe(link);
});

// WHAT PREFETCH ACTUALLY DOES:
// 1. Loads the route's JavaScript chunk
// 2. May also preload data (framework-dependent)
// 3. Stores in memory for instant navigation

Scroll Restoration: The Hidden Complexity

When navigating, browsers must manage scroll position:

// BROWSER DEFAULT BEHAVIOR:
// - New navigation: scroll to top
// - Back/forward: restore previous scroll position

// SPA CHALLENGE:
// Browser doesn't know we "navigated" - URL changed via JavaScript
// Must manually handle scroll:

function navigateTo(path) {
  // Save current scroll position
  const scrollPosition = window.scrollY;
  history.replaceState(
    { ...history.state, scrollY: scrollPosition },
    ''
  );

  // Navigate
  history.pushState({ path, scrollY: 0 }, '', path);

  // Scroll to top for new navigation
  window.scrollTo(0, 0);
}

window.addEventListener('popstate', (event) => {
  renderRoute(window.location.pathname);

  // Restore scroll position for back/forward
  if (event.state?.scrollY !== undefined) {
    // Wait for content to render
    requestAnimationFrame(() => {
      window.scrollTo(0, event.state.scrollY);
    });
  }
});

The Edge Cases Every Router Must Handle

// 1. TRAILING SLASHES
// /about vs /about/ - are they the same?
// Your router must decide and be consistent
// Usually: redirect one to the other

// 2. CASE SENSITIVITY
// /About vs /about - same route?
// URLs are technically case-sensitive
// Best practice: lowercase, redirect others

// 3. ENCODED CHARACTERS
// /products/running%20shoes = /products/running shoes
// Router must decode: decodeURIComponent(path)

// 4. DOUBLE SLASHES
// /products//shoes - valid?
// Should probably normalize: /products/shoes

// 5. DOT SEGMENTS
// /products/../about = /about
// May need to resolve relative paths

// 6. QUERY STRING HANDLING
// /search?q=foo vs /search?q=bar
// Same route, different parameters
// Router matches path, your code handles query

// 7. HASH FRAGMENTS
// /products#reviews
// Hash not sent to server
// Client must scroll to #reviews element

For Framework Authors: Building Routing Systems

Implementation Note: The patterns and code examples below represent one proven approach to building routing systems. Different frameworks take different approaches—React Router uses a declarative model, Next.js uses file-based routing, and SvelteKit combines both. The direction shown here provides core concepts that most routers share. Adapt these patterns based on your framework's component model, build pipeline, and developer experience goals.

Implementing a Route Matcher

// ROUTE MATCHING IMPLEMENTATION

class RouteMatcher {
  constructor() {
    this.routes = [];
  }

  // Add route with pattern
  add(pattern, handler, meta = {}) {
    const { regex, paramNames, score } = this.compilePattern(pattern);
    this.routes.push({ pattern, regex, paramNames, handler, meta, score });
    // Keep sorted by specificity
    this.routes.sort((a, b) => b.score - a.score);
  }

  // Compile pattern to regex
  compilePattern(pattern) {
    const paramNames = [];
    let score = 0;

    const regexStr = pattern
      .split('/')
      .filter(Boolean)
      .map(segment => {
        // Catch-all: [...param]
        if (segment.startsWith('[...') && segment.endsWith(']')) {
          paramNames.push(segment.slice(4, -1));
          score += 1; // Lowest priority
          return '(.+)';
        }

        // Optional: [[param]]
        if (segment.startsWith('[[') && segment.endsWith(']]')) {
          paramNames.push(segment.slice(2, -2));
          score += 5;
          return '([^/]*)';
        }

        // Dynamic: [param]
        if (segment.startsWith('[') && segment.endsWith(']')) {
          paramNames.push(segment.slice(1, -1));
          score += 10;
          return '([^/]+)';
        }

        // Static segment
        score += 100;
        return escapeRegex(segment);
      })
      .join('/');

    return {
      regex: new RegExp(`^/${regexStr}/?$`),
      paramNames,
      score,
    };
  }

  // Match URL to route
  match(pathname) {
    for (const route of this.routes) {
      const match = pathname.match(route.regex);
      if (match) {
        const params = {};
        route.paramNames.forEach((name, i) => {
          const value = match[i + 1];
          // Handle catch-all as array
          if (route.pattern.includes(`[...${name}]`)) {
            params[name] = value ? value.split('/') : [];
          } else {
            params[name] = value;
          }
        });

        return { route, params };
      }
    }
    return null;
  }
}

function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

Building a File-Based Router Generator

// FILE-BASED ROUTING GENERATOR (Build Tool)

import { glob } from 'glob';
import path from 'path';
import { parse } from '@babel/parser';

async function generateRouteManifest(config) {
  const { pagesDir, extensions = ['.tsx', '.jsx', '.ts', '.js'] } = config;

  // Find all route files
  const pattern = `**/*{${extensions.join(',')}}`;
  const files = await glob(pattern, { cwd: pagesDir });

  const routes = [];

  for (const file of files) {
    // Skip special files
    if (file.startsWith('_') || file.includes('/_')) continue;

    const route = await processRouteFile(pagesDir, file);
    if (route) routes.push(route);
  }

  // Sort by specificity
  routes.sort((a, b) => b.score - a.score);

  return routes;
}

async function processRouteFile(pagesDir, file) {
  const fullPath = path.join(pagesDir, file);
  const content = await fs.readFile(fullPath, 'utf-8');
  const ast = parse(content, { sourceType: 'module', plugins: ['jsx', 'typescript'] });

  // Extract route metadata from exports
  const meta = extractRouteMeta(ast);

  // Convert file path to route pattern
  let route = file
    .replace(/\.(tsx?|jsx?)$/, '')    // Remove extension
    .replace(/\/index$/, '')           // /index -> /
    .replace(/\[\.\.\.(\w+)\]/g, '*')  // [...slug] -> *
    .replace(/\[\[(\w+)\]\]/g, ':$1?') // [[id]] -> :id?
    .replace(/\[(\w+)\]/g, ':$1');     // [id] -> :id

  if (!route) route = '/';
  else if (!route.startsWith('/')) route = '/' + route;

  return {
    path: route,
    file: fullPath,
    ...meta,
    score: calculateScore(route),
  };
}

function extractRouteMeta(ast) {
  const meta = {};

  // Look for exported config
  for (const node of ast.program.body) {
    if (node.type === 'ExportNamedDeclaration') {
      if (node.declaration?.declarations?.[0]?.id?.name === 'config') {
        // Extract static config
        meta.config = evaluateStaticObject(node.declaration.declarations[0].init);
      }
    }
  }

  return meta;
}

// Generate route manifest code
function emitRouteManifest(routes) {
  return `
// Auto-generated route manifest
export const routes = [
${routes.map(r => `  {
    path: ${JSON.stringify(r.path)},
    component: () => import(${JSON.stringify(r.file)}),
    meta: ${JSON.stringify(r.meta || {})},
  }`).join(',\n')}
];
`;
}

Implementing Nested Routes and Layouts

// NESTED ROUTING IMPLEMENTATION

class NestedRouter {
  constructor() {
    this.root = { children: [], layout: null, page: null };
  }

  // Build route tree from flat routes
  buildTree(routes) {
    for (const route of routes) {
      this.insertRoute(route);
    }
  }

  insertRoute(route) {
    const segments = route.path.split('/').filter(Boolean);
    let node = this.root;

    for (let i = 0; i < segments.length; i++) {
      const segment = segments[i];
      let child = node.children.find(c => c.segment === segment);

      if (!child) {
        child = { segment, children: [], layout: null, page: null };
        node.children.push(child);
      }

      node = child;
    }

    // Assign component based on file type
    if (route.file.includes('layout')) {
      node.layout = route.component;
    } else if (route.file.includes('page')) {
      node.page = route.component;
    }
  }

  // Match and collect all layouts + page
  match(pathname) {
    const segments = pathname.split('/').filter(Boolean);
    const matched = [];
    let node = this.root;
    const params = {};

    // Always include root layout
    if (node.layout) matched.push({ component: node.layout, params: {} });

    for (const segment of segments) {
      // Find matching child
      let child = node.children.find(c => c.segment === segment);

      // Try dynamic match
      if (!child) {
        child = node.children.find(c => c.segment.startsWith(':'));
        if (child) {
          const paramName = child.segment.slice(1);
          params[paramName] = segment;
        }
      }

      if (!child) return null; // No match

      if (child.layout) {
        matched.push({ component: child.layout, params: { ...params } });
      }

      node = child;
    }

    // Add final page
    if (node.page) {
      matched.push({ component: node.page, params: { ...params }, isPage: true });
    }

    return { matched, params };
  }
}

// Render nested routes with outlet pattern
async function renderNestedRoute(matched) {
  let content = null;

  // Render inside-out (page first, then wrap with layouts)
  for (let i = matched.length - 1; i >= 0; i--) {
    const { component, params, isPage } = matched[i];
    const Component = await component();

    if (isPage) {
      content = createElement(Component.default, { params });
    } else {
      // Layout receives children as outlet
      content = createElement(Component.default, {
        params,
        children: content,
      });
    }
  }

  return content;
}

Route Preloading System

// ROUTE PRELOADING IMPLEMENTATION

class RoutePreloader {
  constructor(router) {
    this.router = router;
    this.preloaded = new Set();
    this.preloading = new Map();
  }

  // Preload on hover
  setupHoverPreload() {
    document.addEventListener('mouseenter', (e) => {
      const link = e.target.closest('a[href]');
      if (!link) return;

      const href = link.getAttribute('href');
      if (href.startsWith('/') && !this.preloaded.has(href)) {
        this.preload(href);
      }
    }, true);
  }

  // Preload visible links
  setupViewportPreload() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const href = entry.target.getAttribute('href');
          if (!this.preloaded.has(href)) {
            // Use requestIdleCallback for low-priority preload
            requestIdleCallback(() => this.preload(href));
          }
        }
      });
    }, { rootMargin: '200px' });

    document.querySelectorAll('a[href^="/"]').forEach(link => {
      observer.observe(link);
    });
  }

  async preload(pathname) {
    if (this.preloading.has(pathname)) {
      return this.preloading.get(pathname);
    }

    const match = this.router.match(pathname);
    if (!match) return;

    const preloadPromise = (async () => {
      // Preload component code
      const componentPromises = match.matched.map(m => m.component());

      // Preload data if route has loader
      const route = match.matched.find(m => m.isPage);
      const dataPromise = route?.loader?.({
        params: match.params,
        preload: true
      });

      await Promise.all([...componentPromises, dataPromise]);
      this.preloaded.add(pathname);
    })();

    this.preloading.set(pathname, preloadPromise);

    try {
      await preloadPromise;
    } finally {
      this.preloading.delete(pathname);
    }
  }
}

Navigation State Management

// NAVIGATION STATE IMPLEMENTATION

class NavigationManager {
  constructor() {
    this.state = 'idle'; // idle | loading | submitting
    this.location = window.location.pathname;
    this.listeners = new Set();
  }

  subscribe(listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  notify() {
    this.listeners.forEach(l => l({
      state: this.state,
      location: this.location,
    }));
  }

  async navigate(to, options = {}) {
    const { replace = false, state = {} } = options;

    this.state = 'loading';
    this.notify();

    try {
      // Run before navigation hooks
      for (const hook of this.beforeHooks) {
        const result = await hook(this.location, to);
        if (result === false) {
          this.state = 'idle';
          this.notify();
          return;
        }
        if (typeof result === 'string') {
          to = result; // Redirect
        }
      }

      // Update URL
      if (replace) {
        history.replaceState(state, '', to);
      } else {
        history.pushState(state, '', to);
      }

      this.location = to;

      // Load and render route
      await this.renderRoute(to);

      // Scroll to top or hash
      if (to.includes('#')) {
        document.querySelector(to.split('#')[1])?.scrollIntoView();
      } else {
        window.scrollTo(0, 0);
      }

    } catch (error) {
      console.error('Navigation failed:', error);
      throw error;
    } finally {
      this.state = 'idle';
      this.notify();
    }
  }
}

// React hook for navigation state
function useNavigation() {
  const [state, setState] = useState({ state: 'idle', location: '' });

  useEffect(() => {
    return navigationManager.subscribe(setState);
  }, []);

  return state;
}

// Usage in components
function LoadingBar() {
  const { state } = useNavigation();

  return state === 'loading' ? <ProgressBar /> : null;
}

Related Skills

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

35.18%
按下载量换算26

Claude

30.48%
按下载量换算22

Cursor

18.23%
按下载量换算13

Gemini CLI

8.7%
按下载量换算6

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills