Recently, I was working on a React Router v7 app where I needed to implement a cookie banner. The app has many routes, and the requirement was to display the cookie banner on every single one of them, without exception. As all the routes share the same layout, it felt natural to add the cookie banner there. In this article, Iāll walk you through how to achieve this.
Step 1: Setting Up the Layout Component
First, letās create a layout component and define how it will be used in our routes. Hereās a simple example:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
export function Layout() {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
</>
);
}
Then, we use the layout in our routing configuration:
// app/routes.ts
import { type RouteConfig, index, layout } from "@react-router/dev/routes";
export default [
layout("layouts/layout.tsx", [index("routes/home.tsx")]),
] satisfies RouteConfig;
Now that we have our layout set up, we can move on to creating the cookie banner.
Step 2: Creating the Cookie Banner Component
Letās start by building a simple cookie banner:
// app/components/cookie-banner.tsx
import { Form } from "react-router";
export function CookieBanner() {
return (
<div>
<p>We're using cookies</p>
<p>...</p>
<Form method="POST">
<button type="submit" name="consent" value="all">
Accept all
</button>
<button type="submit" name="consent" value="necessary">
Only necessary
</button>
</Form>
</div>
);
}
Here, we use the <Form>
component from React Router for issuing POST
requests when users interact with the cookie banner. Weāll revisit the Form
in a moment.
Step 3: Adding the Cookie Banner to the Layout
Now that the cookie banner component is ready, we can include it in the layout to make it appear across all routes:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
import { CookieBanner } from "~/components/cookie-banner";
export default function Layout() {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
<CookieBanner />
</>
);
}
At this point, the cookie banner will render on every page of the app. Try clicking the buttons, however, and youāll encounter a āMethod Not Allowedā error. Letās fix that.
Step 4: Handling Form Actions
The issue stems from missing an action
attribute in the <Form>
. If you donāt specify an action, the formās request is routed to the current page, and React Router doesnāt know how to handle it.
A simple solution is to point the action
to the index route (or any other route that handles the logic). Update the form as follows:
// app/components/cookie-banner.tsx
<Form method="POST" action="/?index">
Now, when the form is submitted, the request will be sent to the index routeās action. Letās define that action to handle the form submission:
// app/routes/home.tsx
export function action() {
// Handle user cookie preferences here
return {};
}
For simplicity, this example doesnāt include the logic to store user preferences. If youād be interested in learning more about that, let me know in the comments - this could be a topic for another article!
Step 5: Improving User Experience: Redirecting Back
Currently, when users submit the form from a route that isnāt the index route, theyāll be redirected back to the index route after submission. This isnāt ideal from a user experience perspective, so letās fix it.
The goal is to redirect users back to the page where they submitted the form. To achieve this, weāll capture the current pageās pathname using the useLocation
hook and send it as part of the form data. Update the cookie banner component like so:
// app/components/cookie-banner.tsx
import { Form, useLocation } from "react-router";
export function CookieBanner() {
const { pathname } = useLocation();
return (
<div>
<p>We're using cookies</p>
<p>...</p>
<Form method="POST" action="/?index">
<input type="hidden" name="redirect-to" value={pathname} />
<button type="submit" name="consent" value="all">
Accept all
</button>
<button type="submit" name="consent" value="necessary">
Only necessary
</button>
</Form>
</div>
);
}
Using the pathname
value, we include a hidden input field named redirect-to
. This ensures the form submits the current pageās URL.
Next, letās modify the index routeās action
to handle this value and redirect the user appropriately:
// app/routes/home.tsx
import { redirect } from "react-router";
import type { Route } from "./+types/home";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const redirectTo = String(formData.get("redirect-to") || "/");
// Handle user preferences here
return redirect(redirectTo);
}
With this setup, users will be redirected back to the page they were on when they submitted their cookie preferences.
Step 6: Conditional Rendering of the Cookie Banner
Another important improvement is ensuring the cookie banner is dismissed once the user interacts with it. We donāt want the banner to reappear every time the page reloads. To achieve this, we can use a loader
in the layout to fetch user preferences and render the banner conditionally.
Hereās an example implementation:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
import { CookieBanner } from "~/components/cookie-banner";
import type { Route } from "./+types/layout";
export function loader() {
return { consent: "unset" }; // Fetch user preferences and return them here
}
export default function Layout({ loaderData }: Route.ComponentProps) {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
{loaderData.consent === "unset" && <CookieBanner />}
</>
);
}
As you can see, the loader
fetches the userās cookie consent preferences (this logic will depend on your implementation). If the user has already provided consent, the cookie banner will not render. If no consent has been provided (consent: "unset"
), the banner is displayed.
This ensures the bannerās behavior is consistent across different routes and reloads.
Wrapping Up
In this article, we implemented a cookie banner in a React Router v7 app that seamlessly works across all routes. We tackled:
- Adding a layout component and integrating a cookie banner into it.
- Handling form submissions and redirecting users back to their previous page.
- Conditionally rendering the cookie banner based on user preferences.
By using React Routerās loader
and action
mechanisms, we achieved a clean and user-friendly solution. If youād like a follow-up article exploring how to persist cookie preferences (e.g., using local storage, an API, or cookies), let me know in the comments - Iād be happy to dive deeper into the topic.
Thanks for reading!