chore(release): add release workflow, update docs, RTK tags, UI perf improvements#158
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR focuses on performance-oriented refactors across the Next.js app: replacing react-virtuoso with a native IntersectionObserver infinite scroll, tightening RTK Query caching primitives (tag types + providesTags), optimizing Docker builds, and improving image/font loading behavior.
Changes:
- Replace
react-virtuosovirtualization with a custom infinite scroll hook and adjust pagination/query-reset behavior. - Introduce RTK Query
tagTypesand addprovidesTagsto events-related endpoints. - Optimize image loading (Next/Image + blur placeholder), introduce code-splitting via dynamic imports, and update Docker build stages + docs.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/redux/events/apiSlice.ts | Adds providesTags to event-related queries to support tag-based caching. |
| src/redux/baseApi.tsx | Defines RTK Query tagTypes for events/tags/playlists/recommendations. |
| src/hooks/useVideoQueryManager.ts | Updates pagination reset logic and query param handling for listings. |
| src/hooks/useInfiniteScroll.ts | New IntersectionObserver-based load-more hook. |
| src/features/VideosListingPage/videosListingPage.tsx | Removes Virtuoso grid; implements native infinite scroll + useGetEventsQuery usage. |
| src/features/VideosListingPage/styled.tsx | Simplifies grid styling to apply directly on the container. |
| src/features/VideosListingPage/skeletonLoader.tsx | Adjusts skeleton layout to match new grid container styling. |
| src/constants/images.ts | Adds a shared blur placeholder data URI constant. |
| src/components/theme/theme-provider.tsx | Sets font display: "swap" for Inter/Roboto Condensed. |
| src/components/VideoCard/videoCard.tsx | Migrates thumbnail rendering to Next/Image, removes hover-preview video, memoizes component. |
| src/components/VideoCard/videoCard.test.tsx | Removes hover-preview tests and updates imports accordingly. |
| src/components/VideoCard/snapshots/videoCard.test.tsx.snap | Updates snapshot output for Next/Image rendering changes. |
| src/components/EmptyState/emptyState.tsx | Switches empty-state image rendering to Next/Image. |
| src/app/videos/results/page.tsx | Uses dynamic import for SearchResultsPage for code splitting. |
| src/app/videos/page.tsx | Uses dynamic import for VideosListingPage for code splitting. |
| src/app/videos/[videoId]/page.tsx | Uses dynamic import for VideoDetail for code splitting. |
| src/app/page.tsx | Uses dynamic import for HomePage for code splitting. |
| next.config.js | Broadens Next/Image remotePatterns to allow any hostname. |
| docs/state-management-and-api-layer.md | Updates RTK Query tagTypes and notes on tag-based caching. |
| docs/project-overview.md | Updates helper libs list after removing react-virtuoso. |
| docs/deployment-and-release.md | Updates Docker multi-stage + caching documentation. |
| docs/ARCHITECTURE.md | Removes now-outdated note about empty tagTypes. |
| Dockerfile | Refactors to a 3-stage build with cache mounts and a corrected runtime CMD. |
Comments suppressed due to low confidence (1)
src/hooks/useVideoQueryManager.ts:48
prevParamsRef+page: paramsChanged ? 1 : pageprevents stale page numbers from being sent after a filter change, but it also meanspagestate can be incremented (via infinite scroll) whileparamsChangedis still true, and those increments won’t affect the query arg until after the effect updatesprevParamsRef. This can lead to jumping directly to later pages onceparamsChangedflips false. Consider exposing anisResettingflag (paramsChanged) to disable load-more until the reset effect runs, or update the “previous params” synchronously when detecting a change.
const prevParamsRef = useRef<typeof parsedParams | null>(null);
useEffect(() => {
setPage(1);
prevParamsRef.current = parsedParams;
}, [parsedParams.tag, parsedParams.playlist, parsedParams.search, parsedParams.order, parsedParams.year]);
const apiParams = useMemo(() => {
const prevParams = prevParamsRef.current;
const paramsChanged =
!prevParams ||
prevParams.tag !== parsedParams.tag ||
prevParams.playlist !== parsedParams.playlist ||
prevParams.search !== parsedParams.search ||
prevParams.order !== parsedParams.order ||
prevParams.year !== parsedParams.year;
return parseNonPassedParams({
...defaultParams,
page: paramsChanged ? 1 : page,
page_size: 12,
ordering: parsedParams.order ? [parsedParams.order] : ["-event_time"],
search: parsedParams.search ?? undefined,
tag: parsedParams.search ? "" : (parsedParams.tag ?? ""),
playlist: parsedParams.search ? "" : (parsedParams.playlist ?? ""),
event_time_after: parsedParams.year ? format(startOfYear(new Date(parsedParams.year)), "yyyy-MM-dd") : undefined,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+150
to
+165
| <> | ||
| <VideoListingContainer> | ||
| {videoData.map((videoCard) => { | ||
| return ( | ||
| <VideoCard | ||
| data={transformVideoToCardData(videoCard)} | ||
| href={`/videos/${videoCard.slug}`} | ||
| key={videoCard.id} | ||
| width="100%" | ||
| /> | ||
| ); | ||
| })} | ||
| </VideoListingContainer> | ||
| {isFetching && <SkeletonLoader count={6} />} | ||
| {hasNextPage && <Box ref={loadMoreRef} height={1} aria-hidden />} | ||
| </> |
Comment on lines
+64
to
74
| <Image | ||
| data-testid="video-card-image" | ||
| alt={data.title} | ||
| height={196} | ||
| width={315} | ||
| src={data.thumbnail || DEFAULT_THUMBNAIL} | ||
| loading="lazy" | ||
| placeholder="blur" | ||
| blurDataURL={BLUR_DATA_URI} | ||
| sizes="315px" | ||
| style={{ borderRadius: "8px" }} |
Comment on lines
+1
to
+3
| /* eslint-disable max-len */ | ||
| export const BLUR_DATA_URI = | ||
| "data:image/jpeg;base64,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"; |
| ## Notes and Gaps | ||
|
|
||
| - RTK Query tags are not currently configured, so tag-based invalidation is not documented. | ||
| - RTK Query tags are configured, and endpoints provide matching tags for automatic cache invalidation on relevant mutations. |
Comment on lines
+9
to
+12
| # Cache npm downloads | ||
| RUN --mount=type=cache,target=/root/.npm \ | ||
| npm ci --prefer-offline --no-audit --progress=false | ||
|
|
Comment on lines
+9
to
+15
| const observer = new IntersectionObserver( | ||
| ([entry]) => { | ||
| if (entry.isIntersecting && canLoadMore && !loading) { | ||
| onLoadMore(); | ||
| } | ||
| }, | ||
| { rootMargin: "400px" } |
3233fdf to
32d7a8a
Compare
d2f21dd to
954f86e
Compare
954f86e to
6b528fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🚀 Description
Set up automated release process with release-it and GitHub Actions. Optimize Dockerfile for better caching/layers. Sync docs with recent changes. Add perf improvements (dynamic page imports, Next Image blur, RTK tags). Bump to v1.3.0.
📌 Summary
Prepares production-ready release tooling, container optimization, comprehensive docs refresh, and targeted perf/UI enhancements for better loading and DX.
🔧 Changes Implemented
.github/workflows/release.ymlfor auto-release on dev→main merge.release-it.jsonfor conventional changelog & GitHub releases (no npm publish)Dockerfile: multi-stage deps caching, standalone outputVideoCard: NextImagew/ blur/placeholder, remove hover video (perf), React.memo, test/snapshot updatesEmptyState: use NextImagedisplay: swaptagTypes/providesTagsfor Event/Tag/Playlist/RecommendationuseVideoQueryManagerpage reset fix w/ refVideosListingPage: autorefetchOnMountOrArgChangenext.config.jsimages*,.prettierrcremove deprecated,tsconfig.jsonremovebaseUrlBLUR_DATA_URI,package.json/lockv1.3.0🛠️ How It Works?
npm run release --ci: bumps version, commits/tags, updates CHANGELOG.md, creates GH release.node_modules, builder reuses, runner lightweight standalone.Edge cases: Release skips if not dev→main; Docker build args for NEXT_PUBLIC_*; images allow all hostnames (review security).
✅ Checklist Before Merging
docker build .&docker-compose upnpm run release --ci --dry-runchangelog📸 Screenshots (if applicable)
N/A (configs/docs/perf)
🔗 Related Issues
https://projects.arbisoft.com/arbisoft/browse/ARBISOFTSESS-189/
📢 Notes for Reviewers
images remotePatterns: "*"broad—restrict if possible post-localhost/prod.