Wordle in Remix: Part 7 - Protected Routes

I help product teams build quality software and lead engineering efforts. Currently working at OpenSpace as a Senior Software Engineer.
Search for a command to run...

I help product teams build quality software and lead engineering efforts. Currently working at OpenSpace as a Senior Software Engineer.
I've really been enjoying Remix. Thanks for sharing this info on protected routes!
Diego Ballesteros (Relatable Code) thanks a lot, glad that it was helpful to you! ๐
Let's create an infinite Wordle clone in Remix! ๐ฟ We will go step by step, from setting up the application to creating pages and implementing game logic using Remix, TypeScript and Tailwind.
This is the eighth article in a series where we create a Wordle clone in Remix! ๐ฟ We go step by step, from setting up the application to creating pages and implementing game logic. In this article, we will improve the user experience, by adding load...
Last month I built a regression-testing harness for a single library upgrade โ React Query v4 โ v5 in one of the front-end apps I'm working on. It's about 800 lines of code across four packages. It ru

Images are heavy. When you're building offline support, storing them alongside your application data is tempting โ and eventually painful. I hit this wall building support for image attachments. The n

Deciding how to approach offline support can be challenging, especially once you move beyond the basics. So far, I focused on persisting data that was already fetched as a result of user actions. This

Software engineering is changing faster than most of us can comfortably adapt to. The capabilities of LLM models change almost monthly. New tools appear every week. Advice that felt solid three months

This is the eighth post in my series about offline support in web applications and the fifth focused on the foreground queue. In the previous article, we covered error handling and retry strategies. A

This is the seventh article in a series where we create a Wordle clone in Remix! ๐ฟ We go step by step, from setting up the application to creating pages and implementing game logic. In this article, we will be adding protections to selected routes in our application to make sure the users can't access some pages too early.
I'm almost certain you noticed that at the moment you can rather easily get to know the word without guessing. Just navigate to either /play/win or /play/loss and just see the word right there. That's no good. ๐ฉ
We have to add necessary protections to the routes to make sure that we can only see the winning pop-up when the user has won the game, and the loss pop-up when they've lost.
There are probably several ways you could go about ensuring that the routes are accessible only under certain conditions in Remix. For us, the protections revolve around accessing the game state. ๐ช
Let's take a look at the winning pop-up as an example. The plan is as follows.
win, let the user continue and show the page./play page.We know already that to run any logic when a user enters a page in Remix we can use a loader function.
We've laid out the plan, and what's left is to turn this into code.
// app/routes/play/win.tsx
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request.headers.get("Cookie"));
const status = session.get("status");
if (status !== "win") {
throw new redirect("/play");
}
return json(
{ word: session.get("word") },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
};
Notice that in the if block we throw the response - in this particular case, it's just following the convention, to indicate that we're breaking out of the typical flow.
There are also functional implications to throwing inside loaders and you can read more about them in the documentation.
Now, let's add a similar block to the loss pop-up page.
// app/routes/play/loss.tsx
export const loader: LoaderFunction = async ({ request }) => {
const session = await getSession(request.headers.get("Cookie"));
const status = session.get("status");
if (status !== "loss") {
throw new redirect("/play");
}
return json(
{ word: session.get("word") },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
};
With that in place, try when you try to navigate to the page you shouldn't have access to at the moment, you're redirected back. Sweet! ๐
There's one more thing we can do. You might have noticed that there's a bit of duplication around the protection logic. We could rephrase our access protection rule to the following.
Require a given game status to read the session cookie. If that requirement is not met, redirect back.
Let's add a helper function in sessions.ts that will reflect this statement.
// app/sessions.ts
import { redirect } from "remix";
import { GameStatus } from "./types";
...
export async function requireSessionStatus(
request: Request,
requiredStatus: GameStatus
) {
const session = await getSession(request.headers.get("Cookie"));
const status = session.get("status");
if (status !== requiredStatus) {
throw new redirect("/play");
}
return session;
}
Now, we can replace the code added in loaders with the following.
// app/routes/play/win.tsx
import { requireSessionStatus } from "~/sessions";
...
export const loader: LoaderFunction = async ({ request }) => {
const session = await requireSessionStatus(request, "win");
return json(
{ word: session.get("word") },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
};
// app/routes/play/loss.tsx
import { requireSessionStatus } from "~/sessions";
...
export const loader: LoaderFunction = async ({ request }) => {
const session = await requireSessionStatus(request, "loss");
return json(
{ word: session.get("word") },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
};
This way we extracted the protection logic out of the routes and got rid of the duplication. ๐ฅณ
Great, we've added necessary protections not to ruin all the fun for the user! โจ Here's a short summary of things went through:
Next up, we will improve the user experience, by changing how we handle client-server communication in our application. See you! ๐
If you liked the article or you have a question, feel free to reach out to me on Twitterโ or add a comment below!