Intro
Well gotta understand Links
(in MDN) better for this before we jump in.
Links is fundamental, cus
- 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
|
- 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
|
- 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:
- 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>
|
- 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
|
- 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?
- 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
|
- Improved User Experience
1
2
3
4
5
|
// Users can:
- Share exact states
- Use browser back/forward
- Bookmark specific views
- Access history
|
- 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
- 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
|
- 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"
|
- 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
- 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
|
- Common Patterns:
1
2
3
4
5
6
|
/users/kody
├── Full page layout
└── /notes
├── Notes layout with sidebar
└── /123
└── Nested inside notes layout
|
- 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
- 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
|
- 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.
Types of Links in Remix
- 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>
|
- 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
|
- 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
- Home Navigation:
1
2
3
4
|
// In header/footer
import { Link } from '@remix-run/react';
<Link to="/">Epic Notes</Link>
|
- Back Navigation:
1
2
3
4
|
// Going up one level in URL
<Link to=".." relative="path">
Back to Kody
</Link>
|
- Feature Navigation:
1
2
3
4
|
// Going to a feature from profile
<Link to="notes">
View Notes
</Link>
|
Important concepts to ingat
- 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
|
- 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
|
- 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:
- Route Hierarchy:
1
2
3
|
- Check route hierarchy with 'remix routes' command
- Understand parent-child relationships
- Be aware of '_' affecting nesting
|
- 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
|
- 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)

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:
- 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))
}
|
- 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"
}
});
}
|
- 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
- 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>
|
- 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
|
- 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:
- 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"
}
});
}
|
- 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:
- 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": "..."
}
});
|
- 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);
}
}
|