Protected routes keep parts of your app safe by checking if a user is allowed before showing the page. Loaders help fetch data or check permissions before the page loads.
Protected routes with loaders in Remix
import { redirect, json } from '@remix-run/node'; export async function loader({ request }) { const user = await getUserFromRequest(request); if (!user) { throw redirect('/login'); } return json({ user }); }
The loader runs before the page renders to fetch data or check permissions.
Use throw redirect('/path') to send users to another page if they are not allowed.
import { redirect, json } from '@remix-run/node'; export async function loader({ request }) { const user = await getUserFromRequest(request); if (!user) { throw redirect('/login'); } return json({ user }); }
import { redirect, json } from '@remix-run/node'; export async function loader({ request }) { const user = await getUserFromRequest(request); if (!user || user.role !== 'admin') { throw redirect('/unauthorized'); } return json({ user }); }
This example shows a protected route in Remix. The loader checks if the user is logged in by looking for a cookie. If not logged in, it redirects to the login page. If logged in, it shows a welcome message.
import { json, redirect } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; // Simulated function to get user info from request async function getUserFromRequest(request) { // For demo, pretend user is logged in if cookie exists const cookie = request.headers.get('Cookie'); if (cookie && cookie.includes('user=loggedin')) { return { name: 'Alice', role: 'user' }; } return null; } export async function loader({ request }) { const user = await getUserFromRequest(request); if (!user) { throw redirect('/login'); } return json({ user }); } export default function ProtectedPage() { const { user } = useLoaderData(); return ( <main> <h1>Welcome, {user.name}!</h1> <p>This is a protected page only visible to logged-in users.</p> </main> ); }
Loaders run on the server before the page shows, so they are good for security checks.
Remember to handle redirects properly to avoid infinite loops.
You can use cookies, tokens, or sessions to identify users in loaders.
Protected routes use loaders to check user access before showing pages.
Use throw redirect() inside loaders to send unauthorized users elsewhere.
Loaders help keep your app safe and user-friendly by controlling access smoothly.
Practice
Solution
Step 1: Understand the role of loaders in Remix
Loaders run before the page renders and can check user permissions.Step 2: Identify the purpose of protected routes
Protected routes restrict access to authorized users only, so loaders check authorization.Final Answer:
To check if the user is authorized before showing the page -> Option AQuick Check:
Loader checks authorization = To check if the user is authorized before showing the page [OK]
- Thinking loaders style pages
- Assuming loaders run after rendering
- Confusing loaders with event handlers
Solution
Step 1: Recall Remix loader redirect syntax
In Remix loaders, you usethrow redirect()to redirect users.Step 2: Compare options
throw redirect('/login') usesthrow redirect('/login'), which is the correct Remix pattern.Final Answer:
throw redirect('/login') -> Option CQuick Check:
Use throw redirect() in loaders = throw redirect('/login') [OK]
- Using return redirect() instead of throw
- Using client-side navigation methods in loaders
- Trying to set window.location in server code
import { redirect } from '@remix-run/node';
export async function loader({ request }) {
const user = await getUser(request);
if (!user) {
throw redirect('/login');
}
return { user };
}Solution
Step 1: Analyze the loader logic
Ifuseris falsy (not logged in), the loader throwsredirect('/login').Step 2: Understand Remix redirect behavior
Throwing redirect sends the browser to the specified URL before rendering the page.Final Answer:
The user is redirected to the /login page -> Option DQuick Check:
Throw redirect sends user away = The user is redirected to the /login page [OK]
- Thinking user sees protected page anyway
- Assuming error message shows instead of redirect
- Believing loader returns null means blank page
import { redirect } from '@remix-run/node';
export async function loader({ request }) {
const user = await getUser(request);
if (!user) {
return redirect('/login');
}
return { user };
}Solution
Step 1: Check redirect usage in loader
In Remix loaders, you mustthrow redirect(), not return it.Step 2: Understand consequences of return redirect()
Returning redirect() does not stop loader execution properly and causes errors.Final Answer:
Using return redirect() instead of throw redirect() causes a runtime error -> Option BQuick Check:
Throw redirect required in loaders = Using return redirect() instead of throw redirect() causes a runtime error [OK]
- Returning redirect() instead of throwing
- Importing redirect from wrong package
- Forgetting async/await on getUser
import { redirect } from '@remix-run/node';
export async function loader({ request }) {
const user = await getUser(request);
if (!user) {
throw redirect('/login');
}
const profile = await getUserProfile(user.id);
return { user, profile };
}Solution
Step 1: Check authorization logic
The loader checks if user exists, and throws redirect if not, protecting the route.Step 2: Confirm data fetching after authorization
Fetching profile after confirming user is safe and logical.Step 3: Validate return structure
Returning both user and profile data together is valid and useful for the page.Final Answer:
This loader correctly protects the route and fetches profile data -> Option AQuick Check:
Check user, throw redirect, then fetch data = This loader correctly protects the route and fetches profile data [OK]
- Fetching data before checking user
- Returning redirect instead of throwing
- Splitting data fetching unnecessarily
Ready to prove it?
Pass the test and this pattern moves unknown
