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 CartDrawer from '@/components/CartDrawer'
|
||||||
import CartFab from '@/components/CartFab'
|
import CartFab from '@/components/CartFab'
|
||||||
import { CartProvider } from '@/context/CartContext'
|
import { CartProvider } from '@/context/CartContext'
|
||||||
|
import ScrollToTop from '@/components/ScrollToTop'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
@ -51,6 +52,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<CartFab />
|
<CartFab />
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<ScrollToTop />
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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,
|
maxAge: process.env.NODE_ENV === 'production' ? '30d' : 0,
|
||||||
setHeaders: (res, filePath) => {
|
setHeaders: (res, filePath) => {
|
||||||
if (filePath.endsWith('.html') || filePath.endsWith('update.json')) {
|
if (filePath.endsWith('.html') || filePath.endsWith('update.json')) {
|
||||||
|
// Never cache HTML or live data
|
||||||
res.setHeader('Cache-Control', 'no-store');
|
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');
|
res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user