diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx
new file mode 100644
index 0000000..602f5f5
--- /dev/null
+++ b/app/admin/layout.tsx
@@ -0,0 +1,5 @@
+import { Suspense } from "react";
+
+export default function AdminLayout({ children }: LayoutProps<"/admin">) {
+ return {children};
+}
diff --git a/components/page-progress.tsx b/components/page-progress.tsx
index aea5dc3..a420e3f 100644
--- a/components/page-progress.tsx
+++ b/components/page-progress.tsx
@@ -1,15 +1,14 @@
"use client"
-import { Suspense, useEffect, useRef, useState } from "react"
-import { usePathname, useSearchParams } from "next/navigation"
+import { useEffect, useRef, useState } from "react"
import { Progress } from "@/components/ui/progress"
function PageProgressContent() {
- const pathname = usePathname()
- const searchParams = useSearchParams()
const [progress, setProgress] = useState(0)
const [isVisible, setIsVisible] = useState(false)
+ const [navigationKey, setNavigationKey] = useState(0)
const intervalRef = useRef | null>(null)
+ const navigationUrlRef = useRef(null)
// Start progress on click
useEffect(() => {
@@ -26,6 +25,7 @@ function PageProgressContent() {
// Only show progress for actual page transitions
if (isInternal && !isSamePage && target.getAttribute("target") !== "_blank") {
+ navigationUrlRef.current = window.location.href
setIsVisible(true)
setProgress(15)
@@ -48,6 +48,26 @@ function PageProgressContent() {
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
useEffect(() => {
if (intervalRef.current) {
@@ -60,6 +80,7 @@ function PageProgressContent() {
const timeout = setTimeout(() => {
setIsVisible(false)
setTimeout(() => setProgress(0), 300) // Reset after hiding
+ navigationUrlRef.current = null
}, 400)
return () => {
clearTimeout(finishTimeout)
@@ -67,7 +88,7 @@ function PageProgressContent() {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [pathname, searchParams])
+ }, [navigationKey])
if (!isVisible && progress === 0) return null
@@ -82,9 +103,5 @@ function PageProgressContent() {
}
export function PageProgress() {
- return (
-
-
-
- )
+ return
}
diff --git a/lib/auth/server.ts b/lib/auth/server.ts
index 0199b12..6848bd6 100644
--- a/lib/auth/server.ts
+++ b/lib/auth/server.ts
@@ -26,6 +26,12 @@ function required(name: string): string {
return value;
}
+function hasAuthConfiguration(): boolean {
+ return ["DATABASE_URL", "BETTER_AUTH_URL", "BETTER_AUTH_SECRET", "ADMIN_EMAIL"].every(
+ (name) => Boolean(process.env[name]),
+ );
+}
+
function createAuth() {
const adminEmail = required("ADMIN_EMAIL");
@@ -109,6 +115,11 @@ export async function getAdminSession(): Promise {
};
}
+ // 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() });
if (
!session?.session ||