fix production prerender without auth config
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
|
||||||
|
export default function AdminLayout({ children }: LayoutProps<"/admin">) {
|
||||||
|
return <Suspense fallback={null}>{children}</Suspense>;
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Suspense, useEffect, useRef, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
import { usePathname, useSearchParams } from "next/navigation"
|
|
||||||
import { Progress } from "@/components/ui/progress"
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
|
||||||
function PageProgressContent() {
|
function PageProgressContent() {
|
||||||
const pathname = usePathname()
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const [progress, setProgress] = useState(0)
|
const [progress, setProgress] = useState(0)
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
|
const [navigationKey, setNavigationKey] = useState(0)
|
||||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||||
|
const navigationUrlRef = useRef<string | null>(null)
|
||||||
|
|
||||||
// Start progress on click
|
// Start progress on click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -26,6 +25,7 @@ function PageProgressContent() {
|
|||||||
|
|
||||||
// Only show progress for actual page transitions
|
// Only show progress for actual page transitions
|
||||||
if (isInternal && !isSamePage && target.getAttribute("target") !== "_blank") {
|
if (isInternal && !isSamePage && target.getAttribute("target") !== "_blank") {
|
||||||
|
navigationUrlRef.current = window.location.href
|
||||||
setIsVisible(true)
|
setIsVisible(true)
|
||||||
setProgress(15)
|
setProgress(15)
|
||||||
|
|
||||||
@@ -48,6 +48,26 @@ function PageProgressContent() {
|
|||||||
return () => document.removeEventListener("click", handleClick)
|
return () => document.removeEventListener("click", handleClick)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Detect completion without reading Next.js URL hooks during prerendering.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isVisible || !navigationUrlRef.current) return
|
||||||
|
const initialUrl = navigationUrlRef.current
|
||||||
|
const checkInterval = window.setInterval(() => {
|
||||||
|
if (window.location.href !== initialUrl) {
|
||||||
|
window.clearInterval(checkInterval)
|
||||||
|
setNavigationKey((key) => key + 1)
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
|
const fallback = window.setTimeout(() => {
|
||||||
|
window.clearInterval(checkInterval)
|
||||||
|
setNavigationKey((key) => key + 1)
|
||||||
|
}, 10_000)
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(checkInterval)
|
||||||
|
window.clearTimeout(fallback)
|
||||||
|
}
|
||||||
|
}, [isVisible])
|
||||||
|
|
||||||
// Finish progress on route change
|
// Finish progress on route change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
@@ -60,6 +80,7 @@ function PageProgressContent() {
|
|||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
setTimeout(() => setProgress(0), 300) // Reset after hiding
|
setTimeout(() => setProgress(0), 300) // Reset after hiding
|
||||||
|
navigationUrlRef.current = null
|
||||||
}, 400)
|
}, 400)
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(finishTimeout)
|
clearTimeout(finishTimeout)
|
||||||
@@ -67,7 +88,7 @@ function PageProgressContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [pathname, searchParams])
|
}, [navigationKey])
|
||||||
|
|
||||||
if (!isVisible && progress === 0) return null
|
if (!isVisible && progress === 0) return null
|
||||||
|
|
||||||
@@ -82,9 +103,5 @@ function PageProgressContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PageProgress() {
|
export function PageProgress() {
|
||||||
return (
|
return <PageProgressContent />
|
||||||
<Suspense fallback={null}>
|
|
||||||
<PageProgressContent />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ function required(name: string): string {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasAuthConfiguration(): boolean {
|
||||||
|
return ["DATABASE_URL", "BETTER_AUTH_URL", "BETTER_AUTH_SECRET", "ADMIN_EMAIL"].every(
|
||||||
|
(name) => Boolean(process.env[name]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createAuth() {
|
function createAuth() {
|
||||||
const adminEmail = required("ADMIN_EMAIL");
|
const adminEmail = required("ADMIN_EMAIL");
|
||||||
|
|
||||||
@@ -109,6 +115,11 @@ export async function getAdminSession(): Promise<AdminSession | null> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Public pages can be prerendered without the runtime auth secret. In that
|
||||||
|
// case the header simply omits the admin link; auth routes still fail loudly
|
||||||
|
// through getAuth() when authentication is actually used.
|
||||||
|
if (!hasAuthConfiguration()) return null;
|
||||||
|
|
||||||
const session = await getAuth().api.getSession({ headers: await headers() });
|
const session = await getAuth().api.getSession({ headers: await headers() });
|
||||||
if (
|
if (
|
||||||
!session?.session ||
|
!session?.session ||
|
||||||
|
|||||||
Reference in New Issue
Block a user