(SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup#367
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughRemoved layout-wide revalidation, enabled CDN/caching for Sanity and blog data, added ISR (3600s) to blog pages, replaced notification polling with visibility-driven refresh, cached blog categories, and removed Speed Insights with Analytics limited to production. (48 words) Changes
Sequence Diagram(s)(Skipped — changes are configuration/optimizations and small control-flow tweaks that do not introduce a new multi-component sequential flow requiring visualization.) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
frontend/components/header/NotificationBell.tsx (2)
80-91:fetchNotificationsmissing fromuseEffectdependency array — wrap inuseCallback.
fetchNotificationsis defined inside the component and referenced in bothuseEffectcalls without being listed in their dependency arrays. While there's no runtime stale-closure bug here (the function only closes over stablesetStaterefs and a static import), React'sexhaustive-depslint rule will flag both effects. Wrapping inuseCallbackis the correct fix.♻️ Proposed refactor
+import { useCallback, useEffect, useRef, useState } from 'react'; -import { useEffect, useRef, useState } from 'react'; - const fetchNotifications = async () => { + const fetchNotifications = useCallback(async () => { try { const data = await getNotifications(); const parsed = data.map(n => ({ ...n, createdAt: new Date(n.createdAt), })); setNotifications(parsed); } catch (error) { console.error(error); } finally { setLoading(false); } - }; + }, []); useEffect(() => { fetchNotifications(); const handleVisibility = () => { if (document.visibilityState === 'visible') { fetchNotifications(); } }; document.addEventListener('visibilitychange', handleVisibility); return () => document.removeEventListener('visibilitychange', handleVisibility); - }, []); + }, [fetchNotifications]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/header/NotificationBell.tsx` around lines 80 - 91, The effect hooks in NotificationBell reference the local function fetchNotifications without listing it in dependencies, triggering exhaustive-deps warnings; wrap fetchNotifications in useCallback (e.g., const fetchNotifications = useCallback(..., [/* stable deps or none */])) so its identity is stable, then include fetchNotifications in the useEffect dependency arrays (the visibilitychange listener effect and initial fetch effect) to satisfy the linter and avoid stale-closure issues.
145-151: MoveKNOWN_TYPESandKnownTypeto module scope.Both are declared inside the component body, meaning a new array and type reference are created on every render. Neither depends on component state or props.
♻️ Proposed refactor
+const KNOWN_TYPES = ['SYSTEM', 'ACHIEVEMENT', 'ARTICLE', 'SHOP'] as const; +type KnownType = (typeof KNOWN_TYPES)[number]; export function NotificationBell() { ... - const KNOWN_TYPES = ['SYSTEM', 'ACHIEVEMENT', 'ARTICLE', 'SHOP'] as const; - type KnownType = (typeof KNOWN_TYPES)[number];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/header/NotificationBell.tsx` around lines 145 - 151, KNOWN_TYPES and KnownType are declared inside the component causing repeated allocations on every render; move them to module scope so they are created once. Extract the const KNOWN_TYPES and the type alias KnownType out of the component body (above the component file's default export) and keep getSafeNotificationType (which can remain in the module or component) referencing those top-level symbols so renders no longer recreate the array/type.frontend/app/[locale]/blog/category/[category]/page.tsx (1)
37-55:categoriesQueryduplicates the cached query in[locale]/layout.tsx— consider reusinggetCachedBlogCategories.The GROQ query at lines 37-42 and the raw
client.fetchcall at line 53 are identical to the query insidegetCachedBlogCategoriesdefined infrontend/app/[locale]/layout.tsx. IfgetCachedBlogCategorieswere extracted to a shared module (e.g.,lib/blog/categories.ts), this page could reuse the cached result instead of issuing a separate Sanity call.With
revalidate = 3600, this only fires at most once per hour, so the runtime impact is low — but extracting the shared function keeps the cache key and TTL consistent across call sites.♻️ Proposed refactor — extract shared cached query
// lib/blog/categories.ts (new file) +import { unstable_cache } from 'next/cache'; +import groq from 'groq'; +import { client } from '@/client'; + +export type Category = { _id: string; title: string }; + +export const getCachedBlogCategories = unstable_cache( + async () => + client.fetch<Category[]>(groq` + *[_type == "category"] | order(orderRank asc) { _id, title } + `), + ['blog-categories'], + { revalidate: 3600, tags: ['blog-categories'] } +);Then in
category/page.tsx:-import { client } from '@/client'; +import { getCachedBlogCategories } from '@/lib/blog/categories'; ... -const categoriesQuery = groq`...`; ... - const categories: Category[] = await client.fetch(categoriesQuery); + const categories = await getCachedBlogCategories();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/`[locale]/blog/category/[category]/page.tsx around lines 37 - 55, The categories GROQ query and direct client.fetch in BlogCategoryPage duplicate the cached logic in getCachedBlogCategories; refactor by extracting getCachedBlogCategories into a shared module (e.g., lib/blog/categories.ts) and replace the local categoriesQuery + client.fetch usage in BlogCategoryPage with a call to getCachedBlogCategories(locale) (keep using Category[] and matchedCategory lookup via slugify(item.title)), ensuring the shared function preserves the same cache TTL (revalidate) and export name so both this page and the existing getCachedBlogCategories caller use the same cached result.frontend/app/[locale]/blog/page.tsx (1)
75-82: Duplicate categories fetch — same as the one cached in[locale]/layout.tsx.Same DRY issue as in
category/[category]/page.tsx: the identical GROQ query is issued via a rawclient.fetchinstead of reusinggetCachedBlogCategories. Extracting it to a shared cached function (see comment incategory/[category]/page.tsx) would apply here too.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/`[locale]/blog/page.tsx around lines 75 - 82, Replace the duplicate raw client.fetch GROQ call that populates the local `categories` variable with a call to the shared cached helper `getCachedBlogCategories` (the same cache used by `[locale]/layout.tsx`), i.e. remove the `client.fetch(groq`...`)` usage in `page.tsx` and call `getCachedBlogCategories()` instead so the query is centralized and cached; ensure the returned value is assigned to `categories` and import `getCachedBlogCategories` where needed.frontend/app/[locale]/blog/[slug]/page.tsx (1)
19-38:generateMetadataissues a separate Sanity fetch that duplicates data fetched byPostDetails.Since the Sanity client sends POST requests, Next.js's native
fetchdeduplication won't merge these two calls within the same ISR render pass. The impact is one extra origin hit every 3600 s per slug — acceptable for now, but worth tracking if Sanity origin quotas become a concern.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/`[locale]/blog/[slug]/page.tsx around lines 19 - 38, generateMetadata currently repeats the Sanity fetch done by PostDetails; extract the Sanity query into a single shared helper (e.g., getPostBySlug or fetchPostWithCache) and have both generateMetadata and PostDetails call that helper; to prevent duplicate origin calls within the same render, wrap that helper with Next/React's cache() (import cache from 'react' or 'next/cache') so repeated calls with the same slug/locale are deduplicated, and update generateMetadata and PostDetails to call the new helper instead of duplicating the client.fetch logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/app/`[locale]/blog/page.tsx:
- Line 9: The page currently exports revalidate = 3600 but unconditionally reads
searchParams in the Server Component (causing full dynamic rendering), so move
the author-filter read into a new Client Component and stop accessing
searchParams in the server page; create an AuthorFilterShell client component
that uses useSearchParams() (and returns null when ?author= is present) and
renders BlogPageHeader when no author filter exists, wrap it in Suspense from
the server page, and remove the unconditional searchParams access from the page
component so the revalidate export becomes effective again.
---
Nitpick comments:
In `@frontend/app/`[locale]/blog/[slug]/page.tsx:
- Around line 19-38: generateMetadata currently repeats the Sanity fetch done by
PostDetails; extract the Sanity query into a single shared helper (e.g.,
getPostBySlug or fetchPostWithCache) and have both generateMetadata and
PostDetails call that helper; to prevent duplicate origin calls within the same
render, wrap that helper with Next/React's cache() (import cache from 'react' or
'next/cache') so repeated calls with the same slug/locale are deduplicated, and
update generateMetadata and PostDetails to call the new helper instead of
duplicating the client.fetch logic.
In `@frontend/app/`[locale]/blog/category/[category]/page.tsx:
- Around line 37-55: The categories GROQ query and direct client.fetch in
BlogCategoryPage duplicate the cached logic in getCachedBlogCategories; refactor
by extracting getCachedBlogCategories into a shared module (e.g.,
lib/blog/categories.ts) and replace the local categoriesQuery + client.fetch
usage in BlogCategoryPage with a call to getCachedBlogCategories(locale) (keep
using Category[] and matchedCategory lookup via slugify(item.title)), ensuring
the shared function preserves the same cache TTL (revalidate) and export name so
both this page and the existing getCachedBlogCategories caller use the same
cached result.
In `@frontend/app/`[locale]/blog/page.tsx:
- Around line 75-82: Replace the duplicate raw client.fetch GROQ call that
populates the local `categories` variable with a call to the shared cached
helper `getCachedBlogCategories` (the same cache used by `[locale]/layout.tsx`),
i.e. remove the `client.fetch(groq`...`)` usage in `page.tsx` and call
`getCachedBlogCategories()` instead so the query is centralized and cached;
ensure the returned value is assigned to `categories` and import
`getCachedBlogCategories` where needed.
In `@frontend/components/header/NotificationBell.tsx`:
- Around line 80-91: The effect hooks in NotificationBell reference the local
function fetchNotifications without listing it in dependencies, triggering
exhaustive-deps warnings; wrap fetchNotifications in useCallback (e.g., const
fetchNotifications = useCallback(..., [/* stable deps or none */])) so its
identity is stable, then include fetchNotifications in the useEffect dependency
arrays (the visibilitychange listener effect and initial fetch effect) to
satisfy the linter and avoid stale-closure issues.
- Around line 145-151: KNOWN_TYPES and KnownType are declared inside the
component causing repeated allocations on every render; move them to module
scope so they are created once. Extract the const KNOWN_TYPES and the type alias
KnownType out of the component body (above the component file's default export)
and keep getSafeNotificationType (which can remain in the module or component)
referencing those top-level symbols so renders no longer recreate the
array/type.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
frontend/actions/notifications.tsfrontend/app/[locale]/blog/[slug]/PostDetails.tsxfrontend/app/[locale]/blog/[slug]/page.tsxfrontend/app/[locale]/blog/category/[category]/page.tsxfrontend/app/[locale]/blog/page.tsxfrontend/app/[locale]/layout.tsxfrontend/app/api/blog-author/route.tsfrontend/app/layout.tsxfrontend/client.tsfrontend/components/header/NotificationBell.tsxfrontend/package.json
💤 Files with no reviewable changes (2)
- frontend/package.json
- frontend/actions/notifications.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
frontend/app/[locale]/blog/page.tsx (1)
83-83:⚠️ Potential issue | 🟠 Major
revalidate = 3600is still defeated —BlogPageHeadercallsuseSearchParams()without aSuspenseboundary.The direct
searchParamsaccess in the server component was correctly removed, butBlogPageHeader(seefrontend/components/blog/BlogPageHeader.tsxlines 9–24) internally callsuseSearchParams(). During static rendering, the entire page is deopted into client-side rendering byuseSearchParamswhen there is noSuspenseboundary that caught it —useSearchParams()causes the tree up to the closestSuspenseboundary to be client-side rendered. During production builds, a static page that callsuseSearchParamsfrom a Client Component must be wrapped in aSuspenseboundary, otherwise the build fails.The
export const revalidate = 3600on line 9 will have no practical effect untilBlogPageHeaderis wrapped in<Suspense>.🛡️ Proposed fix
+import { Suspense } from 'react'; ... - <BlogPageHeader title={t('title')} subtitle={t('subtitle')} /> + <Suspense fallback={null}> + <BlogPageHeader title={t('title')} subtitle={t('subtitle')} /> + </Suspense>Using
fallback={null}is acceptable here sinceBlogPageHeaderalready returnsnullwhen an author filter is active — there is no visible loading state to preserve. Alternatively, you can pass a skeleton or the header markup directly as the fallback so the static shell always shows a header.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/`[locale]/blog/page.tsx at line 83, BlogPageHeader internally calls useSearchParams which forces the tree to client render unless caught by a Suspense boundary, so the export const revalidate = 3600 is ineffective; wrap the BlogPageHeader usage in the page component inside a React.Suspense (e.g., <Suspense fallback={null}> around <BlogPageHeader ... />) so the header's client rendering is isolated and the page can remain statically revalidated, keeping the fallback minimal (fallback={null} or a header skeleton) as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@frontend/app/`[locale]/blog/page.tsx:
- Line 83: BlogPageHeader internally calls useSearchParams which forces the tree
to client render unless caught by a Suspense boundary, so the export const
revalidate = 3600 is ineffective; wrap the BlogPageHeader usage in the page
component inside a React.Suspense (e.g., <Suspense fallback={null}> around
<BlogPageHeader ... />) so the header's client rendering is isolated and the
page can remain statically revalidated, keeping the fallback minimal
(fallback={null} or a header skeleton) as appropriate.
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364) * (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367) * perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR * fix(build): align Netlify Node version and remove SpeedInsights import * chore(release): bump version to 1.0.4 --------- Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com>
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364) * (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367) * perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR * fix(build): align Netlify Node version and remove SpeedInsights import * chore(release): bump version to 1.0.4 * (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * feat(frontend): sync auth state across tabs via BroadcastChannel * (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * fix: eliminate quiz timer flash on language switch Remove Suspense boundary (loading.tsx) that unmounted QuizContainer during locale navigation. Synchronous session restore via useReducer lazy initializer and correct timer initialization via useState lazy initializer prevent any visible state reset on language switch * fix: replace quiz card layout shift with skeleton grid during progress load * chore(release): v1.0.5 --------- Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com> Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364) * (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367) * perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR * fix(build): align Netlify Node version and remove SpeedInsights import * chore(release): bump version to 1.0.4 * (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * feat(frontend): sync auth state across tabs via BroadcastChannel * (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * fix: eliminate quiz timer flash on language switch Remove Suspense boundary (loading.tsx) that unmounted QuizContainer during locale navigation. Synchronous session restore via useReducer lazy initializer and correct timer initialization via useState lazy initializer prevent any visible state reset on language switch * fix: replace quiz card layout shift with skeleton grid during progress load * chore(release): v1.0.5 * (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375) * (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) * (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors * Add shop category images to public * (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks * (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED) * (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants * (SP: 3) [Shop][Monobank] review fixes (tests, logging, success UI) * (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups * (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing * (SP: 1) [Cart] adding route for user orders to cart page * (SP: 1) [Cart] fix after review cart mpage and adding index for orders * (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge * (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change * (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling) * (SP: 2)[Backend] shop/shipping schema migrations foundation * (SP: 2)[Backend] shop/shipping public routes + np cache + sync * (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy * (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping * (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock * (SP: 2)[Backend] shop/shipping: admin+ui shipping actions * (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics * (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests * (SP: 1) [db]: add shop shipping core migration * (SP: 1) [FIX] resolve merge artifacts in order details page * (SP: 1) [FIX] apply post-review fixes for shipping and admin flows * (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code) * (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping * (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed * (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout. * (SP: 1) [FIX] migration 17 * (SP: 1) [DB] migrarion to testind DB and adjusting tests * (SP: 1)[DB] slow down restock janitor + enforce prod interval floor * (SP: 1) [DB] add order status lite view (opt-in) + instrumentation * (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling * (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant) * (SP: 1)[DB] enforce production min intervals for internal shipping jobs * (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes * (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching * (SP: 1)[DB]: slow janitor schedule to every 30 minutes * (SP: 1)[DB] increase polling delays for MonobankRedirectStatus * (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor * (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor --------- Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com> Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364) * (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367) * perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR * fix(build): align Netlify Node version and remove SpeedInsights import * chore(release): bump version to 1.0.4 * (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * feat(frontend): sync auth state across tabs via BroadcastChannel * (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * fix: eliminate quiz timer flash on language switch Remove Suspense boundary (loading.tsx) that unmounted QuizContainer during locale navigation. Synchronous session restore via useReducer lazy initializer and correct timer initialization via useState lazy initializer prevent any visible state reset on language switch * fix: replace quiz card layout shift with skeleton grid during progress load * chore(release): v1.0.5 * (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375) * (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) * (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors * Add shop category images to public * (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks * (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED) * (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants * (SP: 3) [Shop][Monobank] review fixes (tests, logging, success UI) * (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups * (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing * (SP: 1) [Cart] adding route for user orders to cart page * (SP: 1) [Cart] fix after review cart mpage and adding index for orders * (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge * (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change * (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling) * (SP: 2)[Backend] shop/shipping schema migrations foundation * (SP: 2)[Backend] shop/shipping public routes + np cache + sync * (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy * (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping * (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock * (SP: 2)[Backend] shop/shipping: admin+ui shipping actions * (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics * (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests * (SP: 1) [db]: add shop shipping core migration * (SP: 1) [FIX] resolve merge artifacts in order details page * (SP: 1) [FIX] apply post-review fixes for shipping and admin flows * (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code) * (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping * (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed * (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout. * (SP: 1) [FIX] migration 17 * (SP: 1) [DB] migrarion to testind DB and adjusting tests * (SP: 1)[DB] slow down restock janitor + enforce prod interval floor * (SP: 1) [DB] add order status lite view (opt-in) + instrumentation * (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling * (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant) * (SP: 1)[DB] enforce production min intervals for internal shipping jobs * (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes * (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching * (SP: 1)[DB]: slow janitor schedule to every 30 minutes * (SP: 1)[DB] increase polling delays for MonobankRedirectStatus * (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor * (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor * (SP: 3) [SHOP] audit-driven e2e purchase readiness hardening (events, notifications, consent, returns) (#378) * (SP:3)[SHOP] add canonical payment/shipping/admin audit tables + dedupe helper with flagged atomic dual-write * (SP: 2)[SHOP] add INTL quote flow (request/offer/accept/decline), payment-init gate, and quote expiry/timeout sweeps * (SP: 3)[SHOP] introduce outbox-driven notifications with projector + worker (phase 3) * (SP: 3)[SHOP] add minimal returns/RMA lifecycle with atomic audit + canonical events (phase 4) * (SP: 3)[SHOP] enforce guest status-token lite-only access and audit token usage (phase 5) * (SP: 3)[SHOP] centralize transition guards and enforce across admin/webhook/worker flows (phase 6) :wq n * (SP:3)[SHOP]: enforce DATABASE_URL_LOCAL preflight + deterministic vitest config * (SP:3)[SHOP]: require canonical events in prod (fail-fast) * (SP:3)[SHOP]: implement notifications transport with retries + DLQ * (SP:3)[SHOP]: persist checkout legal consent artifact * (SP:1)[SHOP] add migration 0025 for consent + events + audit + prices * (SP:1)[SHOP] emit shipping_events for shipment worker transitions * (SP:2)[SHOP] audit admin product mutations (deduped) * (SP:2)[SHOP] make Monobank webhook retryable on transient apply failures * (SP:3) [SHOP] enforce guest status-token scopes across order actions * (SP:1) [SHOP] make product_prices the only write authority * (SP:1) [SHOP] add Playwright e2e smoke suite (local DB only) * (SP:3) [SHOP] explicitly reject exchanges (EXCHANGES_NOT_SUPPORTED) * (SP: 3)[FIX] harden audit + workers/tests; fix transitions/restock + webhook perf * (SP: 3)[FIX] harden local-db test safety and tighten shop reliability guards * (SP: 3)[FIX] fail-closed admin audit, refine shipments-worker outcomes/metrics, tighten quote+tests * (SP: 1)[FIX] harden shipments-worker claiming/leases and make audit+quote/test paths resilient * (SP: 1)[FIX] harden quote request errors/logging and sanitize requestId; document best-effort delete audit * feat(ui): add devops/cloud category icons and styles (#379) * chore(release): prepare v1.0.6 changelog * chore: bump version to 1.0.6 --------- Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com> Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
* (SP: 3) [Backend] add Nova Poshta shipping foundation + checkout persistence + async label workflow (#364) * (SP: 2) [Frontend] Reduce Vercel variable costs via caching and analytics cleanup (#367) * perf(vercel): cut runtime costs via notification, blog cache, and analytics changes * perf(blog): remove server searchParams usage to preserve ISR * fix(build): align Netlify Node version and remove SpeedInsights import * chore(release): bump version to 1.0.4 * (SP: 2) [Frontend] Remove [locale] layout force-dynamic and move auth to client-side (#370) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * (SP: 2) [Frontend] Reduce auth overhead and sync auth state across tabs (#372) * refactor(frontend): remove locale layout dynamic auth and move header auth client-side * fix(frontend): prevent stale auth responses in useAuth and remove redundant dashboard dynamic layout * feat(frontend): sync auth state across tabs via BroadcastChannel * (SP: 2) [Frontend] Quizzes page ISR + client-side progress + GitHub stars cache (#371) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * (SP: 1) [Frontend] Fix quiz timer flash and card layout shift on quizzes page (#373) * perf(quiz-flow): move quiz progress to client-side fetch, enable ISR for quizzes page - Move user progress fetch from SSR to client-side API (/api/quiz/progress) - Remove force-dynamic and getCurrentUser() from quizzes page - Add revalidate=300 for ISR caching - Use window.history.replaceState for tab URL sync (avoid Next.js navigation) - Add forceMount to TabsContent to prevent layout shift on tab switch - Fix nested <main> — use <section> inside DynamicGridBackground - Cache GitHub stars count in sessionStorage to avoid refetch + re-animation * perf: replace useRef with useState lazy initializer in GitHubStarButton Fixes React 19 react-hooks/refs ESLint error — useRef.current cannot be read during render. Uses useState(getStoredStars) to capture the sessionStorage value once on mount instead. * fix: stop star icon trembling on hover in GitHubStarButton * fix: eliminate quiz timer flash on language switch Remove Suspense boundary (loading.tsx) that unmounted QuizContainer during locale navigation. Synchronous session restore via useReducer lazy initializer and correct timer initialization via useState lazy initializer prevent any visible state reset on language switch * fix: replace quiz card layout shift with skeleton grid during progress load * chore(release): v1.0.5 * (SP: 3)[Shop][DB] Reduce Neon compute: throttle janitor + relax checkout polling + add sweep indexes (#375) * (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) * (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors * Add shop category images to public * (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks * (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED) * (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants * (SP: 3) [Shop][Monobank] review fixes (tests, logging, success UI) * (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups * (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing * (SP: 1) [Cart] adding route for user orders to cart page * (SP: 1) [Cart] fix after review cart mpage and adding index for orders * (SP: 1) [Cart] Fix cart orders summary auth rendering and return totalCount for orders badge * (SP: 1) [Cart] remove console.warn from CartPageClient to satisfy monobank logging safety invariant, namespace localStorage cart by user and reset on auth change * (SP: 1) [Cart] rehydrate per cartOwnerId (remove didHydrate coupling) * (SP: 2)[Backend] shop/shipping schema migrations foundation * (SP: 2)[Backend] shop/shipping public routes + np cache + sync * (SP: 2)[Backend] shop/shipping: shipping persistence + currency policy * (SP: 2)[Backend] shop/shipping: webhook apply + psp fields + enqueue shipping * (SP: 2)[Backend] shop/shipping: shipments worker + internal run + np mock * (SP: 2)[Backend] shop/shipping: admin+ui shipping actions * (SP: 2)[Backend] shop/shipping: retention + log sanitizer + metrics * (SP: 1)[Backend] stabilize Monobank janitor (job1/job3) and fix failing apply-outcomes tests * (SP: 1) [db]: add shop shipping core migration * (SP: 1) [FIX] resolve merge artifacts in order details page * (SP: 1) [FIX] apply post-review fixes for shipping and admin flows * (SP: 1) [FIX] align cart shipping imports (localeToCountry + availability reason code) * (SP: 1) [FIX] hard-block checkout when shipping disabled + i18n reason mapping * (SP: 1) [FIX] harden webhook enqueue + shipping worker + NP catalog + cart fail-closed * (SP: 1) [FIX] Initialize shippingMethodsLoading to true to avoid premature checkout. * (SP: 1) [FIX] migration 17 * (SP: 1) [DB] migrarion to testind DB and adjusting tests * (SP: 1)[DB] slow down restock janitor + enforce prod interval floor * (SP: 1) [DB] add order status lite view (opt-in) + instrumentation * (SP: 1) [DB] replace checkout success router.refresh polling with backoff API polling * (SP: 1) [DB] throttle sessions activity heartbeat + use count(*) (PK invariant) * (SP: 1)[DB] enforce production min intervals for internal shipping jobs * (SP: 1) [DB] add minimal partial indexes for orders sweeps + rollout notes * (SP: 1) [DB] refactor sweep claim step to FOR UPDATE SKIP LOCKED batching * (SP: 1)[DB]: slow janitor schedule to every 30 minutes * (SP: 1)[DB] increase polling delays for MonobankRedirectStatus * (SP: 1)[FIX] harden webhooks + fix SSR hydration + janitor/np gates + sweeps refactor * (SP: 1)[FIX] harden shipping enqueue gating + apply NP interval floor * (SP: 3) [SHOP] audit-driven e2e purchase readiness hardening (events, notifications, consent, returns) (#378) * (SP:3)[SHOP] add canonical payment/shipping/admin audit tables + dedupe helper with flagged atomic dual-write * (SP: 2)[SHOP] add INTL quote flow (request/offer/accept/decline), payment-init gate, and quote expiry/timeout sweeps * (SP: 3)[SHOP] introduce outbox-driven notifications with projector + worker (phase 3) * (SP: 3)[SHOP] add minimal returns/RMA lifecycle with atomic audit + canonical events (phase 4) * (SP: 3)[SHOP] enforce guest status-token lite-only access and audit token usage (phase 5) * (SP: 3)[SHOP] centralize transition guards and enforce across admin/webhook/worker flows (phase 6) :wq n * (SP:3)[SHOP]: enforce DATABASE_URL_LOCAL preflight + deterministic vitest config * (SP:3)[SHOP]: require canonical events in prod (fail-fast) * (SP:3)[SHOP]: implement notifications transport with retries + DLQ * (SP:3)[SHOP]: persist checkout legal consent artifact * (SP:1)[SHOP] add migration 0025 for consent + events + audit + prices * (SP:1)[SHOP] emit shipping_events for shipment worker transitions * (SP:2)[SHOP] audit admin product mutations (deduped) * (SP:2)[SHOP] make Monobank webhook retryable on transient apply failures * (SP:3) [SHOP] enforce guest status-token scopes across order actions * (SP:1) [SHOP] make product_prices the only write authority * (SP:1) [SHOP] add Playwright e2e smoke suite (local DB only) * (SP:3) [SHOP] explicitly reject exchanges (EXCHANGES_NOT_SUPPORTED) * (SP: 3)[FIX] harden audit + workers/tests; fix transitions/restock + webhook perf * (SP: 3)[FIX] harden local-db test safety and tighten shop reliability guards * (SP: 3)[FIX] fail-closed admin audit, refine shipments-worker outcomes/metrics, tighten quote+tests * (SP: 1)[FIX] harden shipments-worker claiming/leases and make audit+quote/test paths resilient * (SP: 1)[FIX] harden quote request errors/logging and sanitize requestId; document best-effort delete audit * feat(ui): add devops/cloud category icons and styles (#379) * chore(release): prepare v1.0.6 changelog * chore: bump version to 1.0.6 * fix(orders): close missing brace in checkout shipping snapshot try block * fix(checkout): correct nested try/catch structure for shipping snapshot * fix(order-status): remove stale responseMode lite branch --------- Co-authored-by: Liudmyla Sovetovs <milkaegik@gmail.com> Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Summary
This PR reduces Vercel variable costs by removing unnecessary server work and improving cache usage.
Changes
revalidatePath('/', 'layout')from notification actions.revalidate = 3600) and removednoStore.blog/[slug]/page.tsx).useCdn: true) and removed localuseCdn: falseoverrides in blog/layout/blog-author.unstable_cache(revalidate: 3600, tag:blog-categories).NODE_ENV === 'production').Out of scope
Expected impact
Closes #366
Summary by CodeRabbit
Performance Improvements
Behavior Changes
Changes