Skip to content

Commit 6f54841

Browse files
authored
fix: getBlitzContext() can only be used in React Server Components in Nextjs 13 or higher (#4299)
* fix: remove custom errors thrown by blitz * Create curvy-cougars-lick.md * use require and eval rather than the await which becomes a `yield import` possibly causing the issue * pnpm lock fix * Update .changeset/curvy-cougars-lick.md * add comment * use correct error type * Apply suggestions from code review
1 parent e8f564e commit 6f54841

File tree

3 files changed

+188
-170
lines changed

3 files changed

+188
-170
lines changed

.changeset/curvy-cougars-lick.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@blitzjs/auth": patch
3+
"blitz": patch
4+
---
5+
6+
fix: getBlitzContext() can only be used in React Server Components in Nextjs 13 or higher

packages/blitz-auth/src/server/auth-sessions.ts

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -182,30 +182,36 @@ export async function getSession(
182182
}
183183

184184
export async function getBlitzContext(): Promise<Ctx> {
185-
const {headers, cookies} = await import("next/headers").catch(() => {
186-
throw new Error(
187-
"getBlitzContext() can only be used in React Server Components in Nextjs 13 or higher",
185+
try {
186+
//using eval to avoid bundling next/headers
187+
const {headers, cookies} = eval("require('next/headers')")
188+
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
189+
cookies: {[key: string]: string}
190+
}
191+
req.headers = Object.fromEntries(headers())
192+
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
193+
if (csrfToken) {
194+
req.headers[HEADER_CSRF] = csrfToken.value
195+
}
196+
req.cookies = Object.fromEntries(
197+
cookies()
198+
.getAll()
199+
.map((c: {name: string; value: string}) => [c.name, c.value]),
188200
)
189-
})
190-
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
191-
cookies: {[key: string]: string}
192-
}
193-
req.headers = Object.fromEntries(headers())
194-
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
195-
if (csrfToken) {
196-
req.headers[HEADER_CSRF] = csrfToken.value
197-
}
198-
req.cookies = Object.fromEntries(
199-
cookies()
200-
.getAll()
201-
.map((c: {name: string; value: string}) => [c.name, c.value]),
202-
)
203-
const res = new ServerResponse(req)
204-
const session = await getSession(req, res, true)
205-
const ctx: Ctx = {
206-
session,
201+
const res = new ServerResponse(req)
202+
const session = await getSession(req, res, true)
203+
const ctx: Ctx = {
204+
session,
205+
}
206+
return ctx
207+
} catch (e) {
208+
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
209+
throw new Error(
210+
"Usage of `useAuthenticatedBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.",
211+
)
212+
}
213+
throw e
207214
}
208-
return ctx
209215
}
210216

211217
interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
@@ -227,58 +233,64 @@ export async function useAuthenticatedBlitzContext({
227233
})
228234
const ctx: Ctx = await getBlitzContext()
229235
const userId = ctx.session.userId
230-
const {redirect} = await import("next/navigation").catch(() => {
231-
throw new Error(
232-
"useAuthenticatedBlitzContext() can only be used in React Server Components in Nextjs 13 or higher",
233-
)
234-
})
235-
if (userId) {
236-
debug("[useAuthenticatedBlitzContext] User is authenticated")
237-
if (redirectAuthenticatedTo) {
238-
if (typeof redirectAuthenticatedTo === "function") {
239-
redirectAuthenticatedTo = redirectAuthenticatedTo(ctx)
236+
try {
237+
//using eval to avoid bundling next/navigation
238+
const {redirect} = eval("require('next/navigation')")
239+
if (userId) {
240+
debug("[useAuthenticatedBlitzContext] User is authenticated")
241+
if (redirectAuthenticatedTo) {
242+
if (typeof redirectAuthenticatedTo === "function") {
243+
redirectAuthenticatedTo = redirectAuthenticatedTo(ctx)
244+
}
245+
const redirectUrl =
246+
typeof redirectAuthenticatedTo === "string"
247+
? redirectAuthenticatedTo
248+
: formatWithValidation(redirectAuthenticatedTo)
249+
debug("[useAuthenticatedBlitzContext] Redirecting to", redirectUrl)
250+
if (role) {
251+
try {
252+
ctx.session.$authorize(role)
253+
} catch (e) {
254+
log.info("Authentication Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
255+
redirect(redirectUrl)
256+
}
257+
} else {
258+
log.info("Authentication Redirect: " + customChalk.dim("(Authenticated)"), redirectUrl)
259+
redirect(redirectUrl)
260+
}
240261
}
241-
const redirectUrl =
242-
typeof redirectAuthenticatedTo === "string"
243-
? redirectAuthenticatedTo
244-
: formatWithValidation(redirectAuthenticatedTo)
245-
debug("[useAuthenticatedBlitzContext] Redirecting to", redirectUrl)
246-
if (role) {
262+
if (redirectTo && role) {
263+
debug("[useAuthenticatedBlitzContext] redirectTo and role are both defined.")
247264
try {
248265
ctx.session.$authorize(role)
249266
} catch (e) {
250-
log.info("Authentication Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
251-
redirect(redirectUrl)
267+
log.error("Authorization Error: " + (e as Error).message)
268+
if (typeof redirectTo !== "string") {
269+
redirectTo = formatWithValidation(redirectTo)
270+
}
271+
log.info("Authorization Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
272+
redirect(redirectTo)
252273
}
253-
} else {
254-
log.info("Authentication Redirect: " + customChalk.dim("(Authenticated)"), redirectUrl)
255-
redirect(redirectUrl)
256274
}
257-
}
258-
if (redirectTo && role) {
259-
debug("[useAuthenticatedBlitzContext] redirectTo and role are both defined.")
260-
try {
261-
ctx.session.$authorize(role)
262-
} catch (e) {
263-
log.error("Authorization Error: " + (e as Error).message)
275+
} else {
276+
debug("[useAuthenticatedBlitzContext] User is not authenticated")
277+
if (redirectTo) {
264278
if (typeof redirectTo !== "string") {
265279
redirectTo = formatWithValidation(redirectTo)
266280
}
267-
log.info("Authorization Redirect: " + customChalk.dim(`Role ${role}`), redirectTo)
281+
log.info("Authentication Redirect: " + customChalk.dim("(Not authenticated)"), redirectTo)
268282
redirect(redirectTo)
269283
}
270284
}
271-
} else {
272-
debug("[useAuthenticatedBlitzContext] User is not authenticated")
273-
if (redirectTo) {
274-
if (typeof redirectTo !== "string") {
275-
redirectTo = formatWithValidation(redirectTo)
276-
}
277-
log.info("Authentication Redirect: " + customChalk.dim("(Not authenticated)"), redirectTo)
278-
redirect(redirectTo)
285+
return ctx as AuthenticatedCtx
286+
} catch (e) {
287+
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
288+
throw new Error(
289+
"Usage of `useAuthenticatedBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.",
290+
)
279291
}
292+
throw e
280293
}
281-
return ctx as AuthenticatedCtx
282294
}
283295

284296
const makeProxyToPublicData = <T extends SessionContextClass>(ctxClass: T): T => {

0 commit comments

Comments
 (0)