Featured image of post Epic Web Dev (EWD)- Routing [Part 2]

Epic Web Dev (EWD)- Routing [Part 2]

Learning Routing

Intro

Well gotta understand Links (in MDN) better for this before we jump in.

Links is fundamental, cus

  1. The URL as a Core Web Feature
1
2
3
4
5
6
7
// In native apps
// No built-in sharing/navigation system
navigation.navigate('UserProfile', { id: 123 })

// On the web
// Natural sharing and navigation
window.location = '/users/123'  // Shareable, bookmarkable
  1. State Management Through URLs
1
2
3
4
5
6
7
8
// Bad: State only in memory
const [selectedUser, setSelectedUser] = useState(null)

// Better: State in URL
/users/123?tab=settings&view=security
// - Shareable
// - Bookmarkable
// - Browser history works naturally
  1. User Experience Benefits:
1
2
3
4
5
6
URLs provide:
├── Bookmarking capability
├── Sharing specific states
├── Browser history navigation
├── SEO benefits
└── Accessibility improvements

Common Blind spots:

  1. Form Navigation
1
2
3
4
5
6
7
8
// Many devs only think of links as:
<a href="/somewhere">Click me</a>

// But forms are also navigation:
<form action="/search" method="get">
  <input name="q" />
  <button>Search</button>
</form>
  1. Route Parameters
1
2
3
4
5
6
7
// Static routes
/products

// Dynamic routes (common but often misunderstood)
/products/:id        // Traditional
/products/$id        // Remix style
/products/[id]       // Next.js style
  1. Nested Routing
1
2
3
4
5
/dashboard
  └── Layout shared
      ├── /dashboard/profile
      ├── /dashboard/settings
      └── /dashboard/messages

Why do I need even bother for this especially for Routing?

  1. Better Architecture Decisions
1
2
3
4
5
6
7
// Instead of:
const Modal = ({ user }) => {
  // State trapped in component
}

// You might realize:
"/users/$userId/edit" // Could be a route
  1. Improved User Experience
1
2
3
4
5
// Users can:
- Share exact states
- Use browser back/forward
- Bookmark specific views
- Access history
  1. SEO and Accessibility
1
2
3
4
// Good URL structure helps:
- Search engines understand your content
- Screen readers navigate your app
- Users understand where they are

TLDR: They’re a fundamental part of web architecture that affects everything from user experience to application state management.


Routing

Lets just jump about Remix Routing vs React Router

  1. File-Based Routing
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
React Router (traditional):
└── Define routes in code
    router.jsx
    ├── <Route path="/users/:id" element={<UserProfile />} />
    └── <Route path="/users/:id/notes" element={<Notes />} />

Remix (file-based):
└── routes/
    ├── users+/
    │   ├── kody.tsx              -> /users/kody
    │   └── kody_+/
    │       ├── notes.tsx         -> /users/kody/notes
    │       └── notes.$id.tsx     -> /users/kody/notes/:id
  1. Layout Nesting vs URL Nesting
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Layout Nesting (Components share UI):
// /users/kody/notes
function NotesLayout() {
  return (
    <div>
      <h1>Notes</h1>
      <Outlet /> {/* Child routes here so dia share dgn parents */}
    </div>
  )
}

// URL Nesting (No shared UI):
-> routes/users+/kody_+/notes.tsx  
-> The "_" tells Remix: "This is just URL nesting, not layout nesting"
  1. Key Route Types:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//1.  Regular Route
// routes/users+/kody.tsx
export default function KodyProfile() {
  return <h1>Kody's Profile</h1>
}

//2. Index Route (default for parent URL)
// routes/users+/kody_+/notes/index.tsx
export default function NotesIndex() {
  return <p>Select a note</p>
}

//3. Dynamic Route (with parameters)
// routes/users+/kody_+/notes.$noteId.tsx
export default function Note() {
  return <h2>Specific Note</h2>
}

Why This Matters

  1. Mental Model Shift:
1
2
3
4
5
6
7
8
9
Old Way (React Router):
- Routes defined in one place
- Manual nesting configuration
- Manual code splitting

New Way (Remix):
- Routes = Files
- Automatic nesting based on file structure
- Built-in code splitting
  1. Common Patterns:
1
2
3
4
5
6
/users/kody
├── Full page layout
└── /notes
    ├── Notes layout with sidebar
    └── /123
        └── Nested inside notes layout
  1. File Naming Convention:
1
2
3
4
+ = Create folder for grouping
_ = URL nesting without layout nesting
. = URL segment separator
$ = Dynamic parameter

The key shift here is thinking of routes as files rather than configuration, and understanding the difference between URL structure and layout structure.

Universal Routing Fundamentals

  1. URL Pattern Types:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
1. Static Routes
   └── /users              - Fixed paths
   
2. Dynamic Routes
   └── /users/:id          - Variable segments
   
3. Index Routes
   └── /users/             - Default views
   
4. Nested Routes
   └── /users/:id/notes    - Hierarchical paths
  1. Core Routing Concepts:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. Route Parameters
/users/$noteId.tsx              // Remix
/users/[noteId].tsx             // Next.js
/users/:noteId                  // Express/React Router

// 2. Layout Nesting
function Layout() {
  return (
    <div>
      <nav>...</nav>
      {children} // or <Outlet /> in Remix/React Router
    </div>
  )
}

// 3. Route Guards/Protection
function ProtectedRoute() {
  if (!isAuthenticated) {
    return redirect('/login')
  }
  return <Component />
}

Let me break down the key concepts from this solution about Links and Routing in Remix.

  1. Absolute Links (Root-based):
1
2
3
// Starts with '/' - Goes to exact path from root
<Link to="/"> Home </Link>
<Link to="/users/kody"> Profile </Link>
  1. Relative Links:
1
2
3
4
5
// Path Relative - Based on URL structure
<Link to=".." relative="path">Back</Link>  // Go up one level

// Route Relative (default) - Based on route hierarchy
<Link to="notes">Notes</Link>  // Add to current path
  1. NavLink - Special Link with Active State:
1
2
3
4
5
6
7
8
9
<NavLink 
  to="notes"
  className={({ isActive }) => `
    underline 
    ${isActive ? 'bg-accent' : ''}
  `}
>
  Notes
</NavLink>

Key Navigation Patterns

  1. Home Navigation:
1
2
3
4
// In header/footer
import { Link } from '@remix-run/react';

<Link to="/">Epic Notes</Link>
  1. Back Navigation:
1
2
3
4
// Going up one level in URL
<Link to=".." relative="path">
  Back to Kody
</Link>
  1. Feature Navigation:
1
2
3
4
// Going to a feature from profile
<Link to="notes">
  View Notes
</Link>

Important concepts to ingat

  1. Relative vs Absolute Paths:
1
2
3
4
5
// Absolute (starts with /)
<Link to="/users/kody">...</Link>  // Always goes to /users/kody

// Relative (no leading /)
<Link to="notes">...</Link>        // Adds to current path
  1. Path vs Route Relativity:
1
2
3
4
5
// Path Relative
relative="path"      // Based on URL segments

// Route Relative (default)
relative="route"     // Based on route hierarchy
  1. Active Link Styling:
1
2
3
4
5
6
7
<NavLink
  className={({ isActive }) => 
    isActive ? 'active-style' : 'regular-style'
  }
>
  Link Text
</NavLink>

Common Gotchas to Watch For:

  1. Route Hierarchy:
1
2
3
- Check route hierarchy with 'remix routes' command
- Understand parent-child relationships
- Be aware of '_' affecting nesting
  1. Path Resolution:
1
2
3
4
5
6
// These might not do what you expect:
<Link to="..">          // Goes to root by default
<Link to="../notes">    // Might skip levels unexpectedly

// Be explicit:
<Link to=".." relative="path">  // Clearly up one URL level
  1. NavLink vs Link:
1
2
3
4
// Use NavLink when you need:
- Active state styling
- Current route indication
- Interactive navigation elements

Decision making Route (for now)

flowchart decision

Layout Decisions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Shared Layout (nested)
routes/
└── dashboard/
    ├── layout.tsx
    ├── profile.tsx
    └── settings.tsx

// Independent Layout (URL-nested)
routes/
└── users_/
    ├── profile.tsx    // Full screen
    └── settings.tsx   // Different layout

Usual Considerations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
1. Authentication Requirements
├── Public Routes
│   └── /login
│   └── /about
└── Protected Routes
    └── /dashboard
    └── /settings

2. Data Loading Patterns
├── Static Data
│   └── marketing pages
├── Dynamic Server Data
│   └── user profiles
└── Real-time Data
    └── chat/messages

3. SEO Requirements
├── Static Generation
│   └── blog posts
└── Server Rendering
    └── user content

What the heck is Resource Routes?

They’re routes that don’t render UI but return other types of responses.

1
2
3
4
5
// Regular UI Route
routes/users.$id.tsx         // Returns JSX/HTML
↓
// Resource Route
routes/api.users.$id.tsx     // Returns JSON/PDF/Images etc

Common Use Cases:

  1. API Endpoints:
1
2
3
4
5
6
7
8
9
// routes/api/users.ts
export async function loader() {
  return json(await getUsers())
}

// routes/api/users.$id.ts
export async function loader({ params }) {
  return json(await getUser(params.id))
}
  1. File Generation:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// routes/reports.$id[.pdf].ts
export async function loader({ params }) {
  const report = await getReport(params.id);
  const pdf = await generatePDF(report);
  
  return new Response(pdf, {
    headers: {
      "Content-Type": "application/pdf"
    }
  });
}
  1. Webhooks:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// routes/webhooks.stripe.ts
export async function action({ request }) {
  if (request.method !== "POST") {
    return json({ error: "Method not allowed" }, 405);
  }

  const payload = await request.json();
  // Handle Stripe webhook
  return json({ success: true });
}

Concepts to know

  1. Linking to Resource Routes:
1
2
3
4
5
6
7
// Must use reloadDocument!
<Link to="export.pdf" reloadDocument>
  Download PDF
</Link>

// Or use regular anchor tag
<a href="/export.pdf">Download PDF</a>
  1. File Extensions in Routes:
1
2
3
// How to handle dots in route names
routes/report.$id[.pdf].ts  → /report/123.pdf
routes/api[.]json.ts        → /api.json
  1. HTTP Methods:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Handle GET requests
export async function loader({ request }) {
  return json({ data: "hello" });
}

// Handle POST/PUT/PATCH/DELETE
export async function action({ request }) {
  switch (request.method) {
    case "POST":
      return handlePost(request);
    case "DELETE":
      return handleDelete(request);
  }
}

Practical Examples:

  1. Dynamic Image Generation:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// routes/og.$slug[.png].ts
export async function loader({ params }) {
  const image = await generateOGImage({
    title: params.slug
  });
  
  return new Response(image, {
    headers: {
      "Content-Type": "image/png",
      "Cache-Control": "public, max-age=31536000"
    }
  });
}
  1. Data Export:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// routes/users.export[.csv].ts
export async function loader() {
  const users = await getUsers();
  const csv = generateCSV(users);
  
  return new Response(csv, {
    headers: {
      "Content-Type": "text/csv",
      "Content-Disposition": "attachment; filename=users.csv"
    }
  });
}

Best Practices:

  1. Response Headers:
1
2
3
4
5
6
7
8
// Always set appropriate headers
return new Response(data, {
  headers: {
    "Content-Type": "...",
    "Cache-Control": "...",
    "Content-Disposition": "..."
  }
});
  1. Error Handling:
1
2
3
4
5
6
7
8
9
// Handle errors appropriately
export async function loader({ request }) {
  try {
    const data = await getData();
    return json(data);
  } catch (error) {
    return json({ error: error.message }, 500);
  }
}
Built with Hugo
Theme Stack designed by Jimmy