Skip to content

Server Side Sessions

@frend-digital/centra supports SSR based GEO location and session management. This is managed using the server side auth() hook

auth()

The Auth hook is a server side only request hook that handles session management and GEO location. If your project was bootstrapped using @frend-digital/cli then this middleware has already been configured for you. You can skip to RSC & Route Handlers, or read on to get an understanding of how it works.

It supports TWO call signatures:

  • Middleware - auth(request, response)

    • This is the full signature, it is meant to be used in the middleware.ts file and executed only once. In this mode it will perform a GEO location request to Centra and set a session cookie on the client.
  • RSC - auth() - The request less mode is meant to be used within React Server Components (RSC) or Route Handlers. This mode is a return only mode, it will not perform a GEO location request and will only parse and return the session cookie as data.

Ideally, both modes are used to produce accurate product results based on the user’s location.

Middleware

In your Next.js project, call the auth() middleware in your middleware.ts file.

src/middleware.ts
import { auth } from "@frend-digital/centra/next";
export const middleware = (request: NextRequest) => {
const response = NextResponse.next();
await auth(request, response);
return response;
};

This will automatically grab the x-real-ip header and forward it to Centra for GEO location. Once the GEO location has been determined, a session cookie will be set on the client in the form of a JWT. The cookie displays the following information:

centra-access-token for a Swedish visitor:

{
"token": "xxxxxxxxxxxxx",
"country": {
"country": "SE",
"name": "Sweden",
"eu": true,
"shipTo": true,
"language": "en",
"currency": "SEK",
"market": "1",
"pricelist": "19"
},
"exp": 1728043567,
"iat": 1725451567
}

RSC and Route Handlers

In your RSC, Server Action or Route Handler, call the auth() hook to retreive the current user session:

src/app/[product]/page.tsx
import { auth } from "@frend-digital/centra/next";
import { notFound } from "next/navigation";
export default async function ProductPage({ params }) {
const userdata = await auth();
if (!userdata) notFound();
const { err, val: product } = await centra.fetchProductByUri(params.uri, {
market: userdata.country.market,
pricelist: userdata.country.pricelist,
});
if (err || !product) notFound();
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: {product.price}</p>
</main>
);
}

In the above example, we server render a product page. The product is fetched based on its URI and the user’s country and pricelist. If the product is unavailable in the user’s market/pricelist we will return a 404.

Partial Pre Rendering Encouraged

PPR (Partial Pre Rendering) is a concept in Next.js that allows you to render a page completely static until the nearest Suspense boundary. In a real world product page, only the price is truly dependent on the user’s location. This means that we can pre-render the product page completely statically, leverage Suspense to Stream in the price and then hydrate the page.

src/app/[product]/page.tsx
import { auth } from "@frend-digital/centra/next";
import { notFound } from "next/navigation";
const Price = async ({ product }) => {
const userdata = await auth();
if (!userdata) notFound();
const pricelist = userdata.country.pricelist;
const userPrice = product.prices[pricelist];
// User's price is not available in the pricelist,
// return a 404 as the product is likely restricted to a specific market.
if (!userPrice) notFound();
return <p>Price: {userPrice.price}</p>;
};
export default async function ProductPage({ params }) {
const { err, val: product } = await centra.fetchProductByUri(params.uri, {
pricelist: "all",
});
if (err || !product) notFound();
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<Suspense fallback={<p>Loading...</p>}>
<Price product={product} />
</Suspense>
</main>
);
}

In the above example, we pre-render the product page, cache it on the CDN and then hydrate the price using Suspense. This is all a single request.

Flow

This is the flow chart of the entire rendering process:

User: {
icon: https://www.svgrepo.com/show/508195/user.svg
}
NextJS: {
icon: https://www.svgrepo.com/show/306466/next-dot-js.svg
Middleware: {
Auth: {
shape: stored_data
label: "auth()"
icon: https://www.svgrepo.com/show/521137/fingerprint.svg
}
Auth -> SetSession: "LocationData"
}
RenderPage: {
Auth: {
shape: stored_data
label: "auth()"
icon: https://www.svgrepo.com/show/521137/fingerprint.svg
}
Auth <-> GetSession: "LocationData"
}
}
Request: {
shape: page
}
Centra: {
GetProducts: {
label: "POST /products"
}
GeoLocation: {
label: "POST /countries/auto"
}
# Response -> SSRResponse: "Return Products Data"
}
User -> Request
Request -> NextJS.Middleware
NextJS.Middleware.SetSession -> Request: "Set-Cookie"
NextJS.Middleware.Auth -> Centra.GeoLocation: "Token Exists = NO"
Centra.GeoLocation -> NextJS.Middleware.Auth: "LocationData"
NextJS.Middleware -> NextJS.RenderPage: "Request"
NextJS.RenderPage -> Centra.GetProducts: "Fetch Products with Session"
Centra.GetProducts -> NextJS.RenderPage
NextJS.RenderPage -> User: "Response"