feat: scroll-to-top button in estore; fix JS/CSS cache headers on main site
- Add ScrollToTop component matching main site's green Top button (appears after 130px scroll, same styling and font) - Fix main-site server.js: JS/CSS now use max-age=3600 + must-revalidate instead of 30d immutable — changes reach users within 1 hour instead of being stuck in browser cache for a month - Images/fonts keep 30d immutable (safe, as they are content-addressed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c6d5a0265f
commit
0576677523
@ -5,6 +5,7 @@ import Footer from '@/components/Footer'
|
||||
import CartDrawer from '@/components/CartDrawer'
|
||||
import CartFab from '@/components/CartFab'
|
||||
import { CartProvider } from '@/context/CartContext'
|
||||
import ScrollToTop from '@/components/ScrollToTop'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@ -51,6 +52,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<CartFab />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
<ScrollToTop />
|
||||
</CartProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
45
estore/src/components/ScrollToTop.tsx
Normal file
45
estore/src/components/ScrollToTop.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export default function ScrollToTop() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
|
||||
setVisible(scrollTop > 130)
|
||||
}
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
if (!visible) return null
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label="Back to top"
|
||||
onClick={() => window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '12px',
|
||||
right: '10px',
|
||||
zIndex: 99,
|
||||
border: '1px solid #363636',
|
||||
outline: 'none',
|
||||
background: '#94d601',
|
||||
cursor: 'pointer',
|
||||
padding: '15px',
|
||||
borderRadius: '10px',
|
||||
fontSize: '18px',
|
||||
boxShadow: '3px 3px 3px #363636',
|
||||
fontFamily: '"Autour One", serif',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.background = '#aedad3' }}
|
||||
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.background = '#94d601' }}
|
||||
>
|
||||
Top
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -64,8 +64,13 @@ const staticCacheOptions = {
|
||||
maxAge: process.env.NODE_ENV === 'production' ? '30d' : 0,
|
||||
setHeaders: (res, filePath) => {
|
||||
if (filePath.endsWith('.html') || filePath.endsWith('update.json')) {
|
||||
// Never cache HTML or live data
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
} else if (/\.(js|css|svg|ico|png|jpg|jpeg|webp|avif|woff2?)$/i.test(filePath)) {
|
||||
} else if (/\.(js|css)$/i.test(filePath)) {
|
||||
// JS/CSS: 1 hour, must revalidate — allows updates to reach users quickly
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600, must-revalidate');
|
||||
} else if (/\.(png|jpg|jpeg|webp|avif|svg|ico|woff2?)$/i.test(filePath)) {
|
||||
// Images/fonts: 30 days immutable (these are named by content, rarely change)
|
||||
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user