Skip to content

Commit c10601e

Browse files
authored
Merge pull request #359 from codeunia-dev/fix/cacheissue
Fix Sign-In/Sign-Up Button Visibility, Hydration Delay & Cache Behaviour
2 parents 8e2e80f + 4ee5534 commit c10601e

File tree

3 files changed

+83
-59
lines changed

3 files changed

+83
-59
lines changed

components/header.tsx

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default function Header() {
6464
return pathname.startsWith(path)
6565
}
6666

67-
67+
6868
const navItems = [
6969
{ href: "/", label: "Home" },
7070
{ href: "/about", label: "About" },
@@ -92,19 +92,17 @@ export default function Header() {
9292
<Link
9393
key={item.href}
9494
href={item.href}
95-
className={`text-sm font-medium transition-colors relative group ${
96-
isActive(item.href)
95+
className={`text-sm font-medium transition-colors relative group ${isActive(item.href)
9796
? "text-primary"
9897
: "text-foreground hover:text-primary"
99-
}`}
98+
}`}
10099
>
101100
{item.label}
102101
<span
103-
className={`absolute -bottom-1 left-0 h-0.5 bg-primary transition-all duration-300 ${
104-
isActive(item.href)
102+
className={`absolute -bottom-1 left-0 h-0.5 bg-primary transition-all duration-300 ${isActive(item.href)
105103
? "w-full"
106104
: "w-0 group-hover:w-full"
107-
}`}
105+
}`}
108106
></span>
109107
</Link>
110108
))}
@@ -114,9 +112,14 @@ export default function Header() {
114112
<div className="hidden md:flex items-center space-x-3 flex-shrink-0">
115113
{/* <ThemeToggle /> */}
116114
{loading ? (
117-
<div className="text-sm text-muted-foreground">Loading...</div>
118-
) : user ? (
119115
<div className="flex items-center space-x-3">
116+
{/* Skeleton for Sign In button */}
117+
<div className="w-[70px] h-[34px] bg-muted/50 rounded-md animate-pulse" />
118+
{/* Skeleton for Sign Up button */}
119+
<div className="w-[75px] h-[34px] bg-muted/50 rounded-md animate-pulse" />
120+
</div>
121+
) : user ? (
122+
<div className="flex items-center space-x-3" key={user.id}>
120123
<PremiumButton user={user} />
121124
<UserDisplay userId={user.id} showCodeuniaId={false} />
122125
<UserIcon />
@@ -143,9 +146,8 @@ export default function Header() {
143146
variant="ghost"
144147
size="icon"
145148
onClick={() => setIsMenuOpen(!isMenuOpen)}
146-
className={`hover:scale-105 transition-all duration-200 ml-1 w-8 h-8 ${
147-
isMenuOpen ? 'bg-muted/50' : ''
148-
}`}
149+
className={`hover:scale-105 transition-all duration-200 ml-1 w-8 h-8 ${isMenuOpen ? 'bg-muted/50' : ''
150+
}`}
149151
>
150152
{isMenuOpen ? <X className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
151153
</Button>
@@ -157,11 +159,11 @@ export default function Header() {
157159
{isMenuOpen && (
158160
<>
159161
{/* Backdrop overlay */}
160-
<div
162+
<div
161163
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[60] md:hidden animate-in fade-in duration-200"
162164
onClick={() => setIsMenuOpen(false)}
163165
/>
164-
166+
165167
{/* Side drawer */}
166168
<div className="mobile-menu-container fixed top-0 right-0 bottom-0 w-[280px] max-w-[85vw] bg-background border-l shadow-2xl z-[70] md:hidden animate-in slide-in-from-right duration-300">
167169
<nav className="flex flex-col h-full">
@@ -186,11 +188,10 @@ export default function Header() {
186188
<Link
187189
key={item.href}
188190
href={item.href}
189-
className={`block text-sm font-medium transition-colors py-2.5 px-3 rounded-md relative ${
190-
isActive(item.href)
191+
className={`block text-sm font-medium transition-colors py-2.5 px-3 rounded-md relative ${isActive(item.href)
191192
? "text-primary font-semibold bg-primary/10"
192193
: "text-foreground hover:text-primary hover:bg-muted/50"
193-
}`}
194+
}`}
194195
onClick={() => setIsMenuOpen(false)}
195196
>
196197
{item.label}
@@ -213,7 +214,7 @@ export default function Header() {
213214
<Shield className="h-4 w-4" />
214215
<span>Dashboard</span>
215216
</Link>
216-
217+
217218
{/* Logout Button */}
218219
<button
219220
onClick={handleLogout}

lib/hooks/useAuth.ts

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export function useAuth() {
1212
const [user, setUser] = useState<User | null>(null)
1313
const [loading, setLoading] = useState(true)
1414
const [error, setError] = useState<string | null>(null)
15-
const [isHydrated, setIsHydrated] = useState(false)
1615
const [is_admin, setIsAdmin] = useState(false)
1716

1817
// Optimized profile fetching with caching
@@ -40,29 +39,23 @@ export function useAuth() {
4039
}, [])
4140

4241
useEffect(() => {
43-
setIsHydrated(true)
44-
}, [])
45-
46-
useEffect(() => {
47-
if (!isHydrated) return
48-
4942
let mounted = true
5043

5144
const initializeAuth = async () => {
5245
try {
5346
const supabase = createClient()
54-
47+
5548
// Get initial session first
5649
const { data: { session }, error: sessionError } = await supabase.auth.getSession()
57-
50+
5851
if (mounted) {
5952
if (sessionError) {
6053
console.error('Session error:', sessionError)
6154
setError(sessionError.message)
6255
setIsAdmin(false)
6356
} else {
6457
setUser(session?.user ?? null)
65-
58+
6659
// Check admin status from profiles table with caching
6760
if (session?.user) {
6861
const profile = await fetchUserProfile(session.user.id)
@@ -79,15 +72,15 @@ export function useAuth() {
7972
async (event, session) => {
8073
if (mounted) {
8174
setUser(session?.user ?? null)
82-
75+
8376
// Check admin status from profiles table with caching
8477
if (session?.user) {
8578
const profile = await fetchUserProfile(session.user.id)
8679
setIsAdmin(profile?.is_admin || false)
8780
} else {
8881
setIsAdmin(false)
8982
}
90-
83+
9184
setLoading(false)
9285
}
9386
}
@@ -108,16 +101,11 @@ export function useAuth() {
108101
return () => {
109102
mounted = false
110103
}
111-
}, [isHydrated, fetchUserProfile])
112-
113-
// Return loading state during hydration
114-
if (!isHydrated) {
115-
return { user: null, loading: true, error: null, is_admin: false }
116-
}
104+
}, [fetchUserProfile])
117105

118-
return {
119-
user,
120-
loading,
106+
return {
107+
user,
108+
loading,
121109
error,
122110
is_admin
123111
}

next.config.ts

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ const nextConfig: NextConfig = {
99
generateBuildId: async () => {
1010
// Use environment-specific build IDs for better cache control
1111
const timestamp = Date.now()
12-
const gitCommit = process.env.VERCEL_GIT_COMMIT_SHA?.substring(0, 7) ||
13-
process.env.GITHUB_SHA?.substring(0, 7) ||
14-
Math.random().toString(36).substring(7)
12+
const gitCommit = process.env.VERCEL_GIT_COMMIT_SHA?.substring(0, 7) ||
13+
process.env.GITHUB_SHA?.substring(0, 7) ||
14+
Math.random().toString(36).substring(7)
1515
const buildId = `${timestamp}-${gitCommit}`
1616
console.log(`🏗️ Build ID: ${buildId}`)
1717
return buildId
@@ -28,7 +28,7 @@ const nextConfig: NextConfig = {
2828
crypto: false,
2929
}
3030
}
31-
31+
3232
return config
3333
},
3434

@@ -107,15 +107,15 @@ const nextConfig: NextConfig = {
107107
const isDev = process.env.NODE_ENV === 'development'
108108
const isProd = process.env.NODE_ENV === 'production'
109109
const buildId = process.env.BUILD_ID || Date.now().toString()
110-
110+
111111
return [
112112
// STATIC IMMUTABLE: Static assets (build files, immutable resources)
113113
{
114114
source: '/_next/static/:path*',
115115
headers: [
116116
{
117117
key: 'Cache-Control',
118-
value: isDev
118+
value: isDev
119119
? 'no-cache, no-store, must-revalidate'
120120
: 'public, max-age=31536000, immutable', // 1 year immutable
121121
},
@@ -137,14 +137,14 @@ const nextConfig: NextConfig = {
137137
},
138138
],
139139
},
140-
140+
141141
// STATIC IMMUTABLE: Images and media files
142142
{
143143
source: '/(images|media|assets)/:path*.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf|eot)',
144144
headers: [
145145
{
146146
key: 'Cache-Control',
147-
value: isDev
147+
value: isDev
148148
? 'no-cache, no-store, must-revalidate'
149149
: 'public, max-age=2592000, immutable', // 30 days immutable
150150
},
@@ -158,14 +158,41 @@ const nextConfig: NextConfig = {
158158
},
159159
],
160160
},
161-
161+
162+
// HOMEPAGE: No caching to ensure auth state is always fresh
163+
{
164+
source: '/',
165+
headers: [
166+
{
167+
key: 'Cache-Control',
168+
value: 'no-cache, no-store, must-revalidate',
169+
},
170+
{
171+
key: 'CDN-Cache-Control',
172+
value: 'no-cache',
173+
},
174+
{
175+
key: 'Pragma',
176+
value: 'no-cache',
177+
},
178+
{
179+
key: 'Vary',
180+
value: 'Cookie, Accept-Encoding',
181+
},
182+
{
183+
key: 'X-Build-ID',
184+
value: buildId,
185+
},
186+
],
187+
},
188+
162189
// DYNAMIC CONTENT: Dynamic pages (events, hackathons, etc.)
163190
{
164191
source: '/(hackathons|events|leaderboard|opportunities)/:path*',
165192
headers: [
166193
{
167194
key: 'Cache-Control',
168-
value: isDev
195+
value: isDev
169196
? 'no-cache, no-store, must-revalidate'
170197
: 'public, max-age=60, stale-while-revalidate=300', // 1min cache, 5min SWR
171198
},
@@ -181,16 +208,20 @@ const nextConfig: NextConfig = {
181208
key: 'Cache-Tag',
182209
value: 'content',
183210
},
211+
{
212+
key: 'Vary',
213+
value: 'Cookie, Accept-Encoding',
214+
},
184215
],
185216
},
186-
217+
187218
// DATABASE QUERIES: API routes that query database
188219
{
189220
source: '/api/(hackathons|leaderboard|tests|verify-certificate)/:path*',
190221
headers: [
191222
{
192223
key: 'Cache-Control',
193-
value: isDev
224+
value: isDev
194225
? 'no-cache, no-store, must-revalidate'
195226
: 'public, max-age=300, stale-while-revalidate=600', // 5min cache, 10min SWR
196227
},
@@ -208,7 +239,7 @@ const nextConfig: NextConfig = {
208239
},
209240
],
210241
},
211-
242+
212243
// USER PRIVATE: Auth and user-specific routes
213244
{
214245
source: '/(protected|admin|profile|dashboard|auth)/:path*',
@@ -227,20 +258,20 @@ const nextConfig: NextConfig = {
227258
},
228259
],
229260
},
230-
261+
231262
// API STANDARD: General API routes and public pages
232263
{
233264
source: '/((?!_next|protected|admin|profile|dashboard|auth).*)',
234265
headers: [
235266
{
236267
key: 'Cache-Control',
237-
value: isDev
268+
value: isDev
238269
? 'no-cache, no-store, must-revalidate'
239-
: 'public, max-age=120, stale-while-revalidate=300', // 2min cache, 5min SWR
270+
: 'public, max-age=0, must-revalidate', // Always revalidate HTML
240271
},
241272
{
242273
key: 'CDN-Cache-Control',
243-
value: isProd ? 'public, max-age=120, stale-while-revalidate=300' : 'no-cache',
274+
value: isProd ? 'public, max-age=60, stale-while-revalidate=300' : 'no-cache',
244275
},
245276
{
246277
key: 'Cache-Tag',
@@ -250,6 +281,10 @@ const nextConfig: NextConfig = {
250281
key: 'X-Build-ID',
251282
value: buildId,
252283
},
284+
{
285+
key: 'Vary',
286+
value: 'Cookie, Accept-Encoding',
287+
},
253288
// Security headers
254289
{ key: 'X-Content-Type-Options', value: 'nosniff' },
255290
{ key: 'X-Frame-Options', value: 'DENY' },
@@ -261,13 +296,13 @@ const nextConfig: NextConfig = {
261296
},
262297

263298
reactStrictMode: false,
264-
299+
265300
// Optimize for production builds
266301
// swcMinify is now default in Next.js 15
267-
302+
268303
// Enable static optimization
269304
trailingSlash: false,
270-
305+
271306
// Optimize for faster builds
272307
onDemandEntries: {
273308
maxInactiveAge: 25 * 1000,

0 commit comments

Comments
 (0)