CacheComponents issue with OAuth
Unanswered
Yellow-breasted Bunting posted this in #help-forum
Yellow-breasted BuntingOP
I have an issue with Next.js 16.0.1 where I am just tried to enable CacheComponents, After doing so i've recieved numerous issues. Mainly happening inside of an OAuth API Callback and inside of another API which fetches data.
They both happen when doing:
These are the exact error messages I am receiving:
I've tried using an CacheLife on Max, but that makes it static and I don't think that should be happening. I have tried using Chat-GPT to resolve my issue, but that one is stuck on depracted data. My goal is to fix these errors efficiently so that I can start caching components/files/functions.
They both happen when doing:
new URL(req.url);. These are the exact error messages I am receiving:
OAuth callback error: Error: Route /api/auth/roblox/callback needs to bail out of prerendering at this point because it used request.url.
at y (src\app\api\auth\roblox\callback\route.js:16:29)
14 | export async function GET(req) {
15 | try {
> 16 | const url = new URL(req.url);
| ^
17 | const code = url.searchParams.get("code");
18 | const returnedState = url.searchParams.get("state");
19 | {
digest: 'NEXT_PRERENDER_INTERRUPTED'API /games error: Error: Route /api/games needs to bail out of prerendering at this point because it used request.url.
at w (src\app\api\games\route.js:5:42)
3 | export async function GET(req) {
4 | try {
> 5 | const { searchParams } = new URL(req.url);
| ^
6 |
7 | const cursorParam = searchParams.get("cursor");
8 | const cursor = cursorParam ? JSON.parse(cursorParam) : null; {
digest: 'NEXT_PRERENDER_INTERRUPTED'I've tried using an CacheLife on Max, but that makes it static and I don't think that should be happening. I have tried using Chat-GPT to resolve my issue, but that one is stuck on depracted data. My goal is to fix these errors efficiently so that I can start caching components/files/functions.
27 Replies
Yellow-breasted BuntingOP
// app/api/auth/roblox/callback/route.js
import { NextResponse } from "next/server";
import { supabaseServer } from "@/utils/supabase/server";
import { createSessionToken } from "@/utils/auth";
async function postForm(url, data) {
return fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(data).toString(),
});
}
export async function GET(req) {
try {
const url = new URL(req.url);
const code = url.searchParams.get("code");
const returnedState = url.searchParams.get("state");
if (!code) return new Response("Missing oauth code", { status: 400 });
const cookieState = req.cookies.get("rbx_oauth_state")?.value;
if (!cookieState || cookieState !== returnedState) {
return new Response("Invalid state (possible CSRF)", { status: 400 });
}
const tokenRes = await postForm("https://apis.roblox.com/oauth/v1/token", {
grant_type: "authorization_code",
code,
client_id: process.env.ROBLOX_CLIENT_ID,
client_secret: process.env.ROBLOX_CLIENT_SECRET,
redirect_uri: process.env.ROBLOX_REDIRECT_URI,
});
if (!tokenRes.ok) {
const txt = await tokenRes.text();
console.error("Roblox token exchange failed:", tokenRes.status, txt);
return new Response("Failed to exchange token", { status: 500 });
}
const tokenData = await tokenRes.json();
const accessToken = tokenData.access_token;
if (!accessToken) {
console.error("No access token in token response", tokenData);
return new Response("Failed to get access token", { status: 500 });
}
const profileRes = await fetch("https://apis.roblox.com/oauth/v1/userinfo", {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!profileRes.ok) {
const txt = await profileRes.text();
console.error("Roblox userinfo failed:", profileRes.status, txt);
return new Response("Failed to fetch userinfo", { status: 500 });
}
const profile = await profileRes.json();
const robloxId = profile.sub || profile.id || profile.userId || profile.user_id;
if (!robloxId) {
console.error("Failed to parse roblox id from profile:", profile);
return new Response("Invalid userinfo", { status: 500 });
}
const supabase = supabaseServer();
const { data: allowed, error } = await supabase
.from("allowed_users")
.select("roblox_user_id")
.eq("roblox_user_id", String(robloxId))
.maybeSingle();
if (error) {
console.error("DB error checking allowed_users:", error);
return new Response("Server error", { status: 500 });
}
if (!allowed) {
const res = NextResponse.json({ error: "Unauthorized" }, { status: 401 });
res.cookies.set("rbx_oauth_state", "", { path: "/", maxAge: 0 });
return res;
}
const token = createSessionToken({ robloxId, expiresInSeconds: 7 * 24 * 3600 });
const redirectUrl = new URL("/dashboard", req.url);
const response = NextResponse.redirect(redirectUrl);
response.cookies.set("session", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 7 * 24 * 3600,
});
response.cookies.set("rbx_oauth_state", "", { path: "/", maxAge: 0 });
return response;
} catch (err) {
console.error("OAuth callback error:", err);
return new Response("Internal server error", { status: 500 });
}
}After reading a bit online I've seen you can just ignore this and it doens't affect the build either. Other errors affected the build I assume? Eitherway I'd rather fix this though.
Yellow-breasted BuntingOP
Got it, also @alfonsüs ardani can you give me some tips on what I should cache? Right now I've cached my queries and some page components, but I am not 100% sure what I should cache. There's some components that have settings, but I am not sure why I should cache that? I don't understand how the caching works on an deeper level so a bit of an insight would help.
I tried looking at the docs for CacheComponents, CacheTags, UpdateTags, Use Cahce and all of that, but I think having an personal answer would help me more in the direction. Because I feel like only caching 6 files isn't enough when I got like 30 components.
I tried looking at the docs for CacheComponents, CacheTags, UpdateTags, Use Cahce and all of that, but I think having an personal answer would help me more in the direction. Because I feel like only caching 6 files isn't enough when I got like 30 components.
@Yellow-breasted Bunting Got it, also <@194128415954173952> can you give me some tips on what I should cache? Right now I've cached my queries and some page components, but I am not 100% sure what I should cache. There's some components that have settings, but I am not sure why I should cache that? I don't understand how the caching works on an deeper level so a bit of an insight would help.
I tried looking at the docs for CacheComponents, CacheTags, UpdateTags, Use Cahce and all of that, but I think having an personal answer would help me more in the direction. Because I feel like only caching 6 files isn't enough when I got like 30 components.
in my experience, you should be caching data fetches that rarely changes (unless provoked) like getAllPosts getAllABCs. Components are okay to cache as long as it doesn't require runtime data.
you definetly shouldn't cache things that requires runtime data like auth, cookies, headers, or dynamic stuff like RNG or fileread.
you definetly shouldn't cache things that requires runtime data like auth, cookies, headers, or dynamic stuff like RNG or fileread.
its a whole new mental model but the key that i often follow is that with caching, the datafetching will tend to tgravitate towards overfetching which is okay since it is only done once or twice for every time the data is changed via updateTags
to determine caching is enough or not, you shouldn't determine it ased on the number of files or the number of components.
you shold check whether your site flows smoothly and navigately smoothly and see whether any loading UI showed up since thats IMO what caching is trying to solve.
you shold check whether your site flows smoothly and navigately smoothly and see whether any loading UI showed up since thats IMO what caching is trying to solve.
the idea or goal of caching is to make your Multi-page app (MPA) feels like native app and have little to zero loading screen since all things are already cached and shows instantly.
Yellow-breasted BuntingOP
First render took 3 seconds for dashboard and pages were kinda fast, after the cache hit it was insanely fast not even seeing the page render it's just instant
go try your app, if theres any part thats slow, check out what method of dat fetching are you using, is it cached?
and if your page is statically rendered using cacheComponent or generateStaticParams, check if accessing an unrendered slug produce noticeable delay
and if your page is statically rendered using cacheComponent or generateStaticParams, check if accessing an unrendered slug produce noticeable delay
Yellow-breasted BuntingOP
During build
during dev its a lot slower
yeah thats to be expected for SSR routes
Yellow-breasted BuntingOP
Build:
Dev:
utils/queries/robloxIDtoUser.js - getUsernames: HIT!
utils/queries/games.js - getGames: HIT!
utils/queries/gameRevenueTotals.js - getRevenueData: HIT!
utils/queries/robloxIDtoUser.js - getUsernames: HIT!
utils/queries/games.js - getGames: HIT!
utils/queries/jobOffers.js - getJobOffers: HIT!
utils/queries/games.js - getGames: HIT!Dev:
utils/queries/robloxIDtoUser.js - getUsernames: HIT!
Cache utils/queries/robloxIDtoUser.js - getUsernames: HIT!
utils/queries/games.js - getGames: HIT!
Cache utils/queries/games.js - getGames: HIT!
utils/queries/gameRevenueTotals.js - getRevenueData: HIT!
Cache utils/queries/gameRevenueTotals.js - getRevenueData: HIT!
utils/queries/robloxIDtoUser.js - getUsernames: HIT!
Cache utils/queries/robloxIDtoUser.js - getUsernames: HIT!
Cache utils/queries/robloxIDtoUser.js - getUsernames: HIT!
Cache utils/queries/games.js - getGames: HIT!
Cache utils/queries/gameRevenueTotals.js - getRevenueData: HIT!
Cache utils/queries/robloxIDtoUser.js - getUsernames: HIT!it could be even faster by statically prerendering the shell of the dashboard and only show Loading UI using <Suspense> for the 3 second it take to load the data where it needed
Yellow-breasted BuntingOP
I think I have that
export default function DashboardLayout({ children }) {
return (
<div className="h-screen p-8 antialiased overflow-hidden">
<Suspense fallback={<div>Loading dashboard...</div>}>
{children}
</Suspense>
</div>
);
}but if thats not worth the headache thats good enough, since on average it evens out (only first user would need to bear the 3 seconds lol)
Yellow-breasted BuntingOP
I just have to make the skeleton for this
Although, you don't even see the loading dashboard... that's how fast it is when cached.
yes so the only time the loading dashbaord is seen is the first time it is build which is uhh
i mean at this point id rather spend my energy on [slug] (if there is any)
Yellow-breasted BuntingOP
There's not
it's simple