feat: 2026 redesign, SEO pages, UX fixes and cover art improvements#247
Conversation
UI / Design - Full 2026 hi-fi redesign: petrol-blue palette, Satoshi font, dark/light section alternation, smooth scroll - New SiteHeader with compact "← New search" variant on /fetch, white logo (brightness-0 invert filter), dark bg (stroy-800) for proper contrast - New SiteFooter with 4-column grid, legal links, stroy-900 dark background - Landing page rewrite: Hero, How it works, Formats, FAQ+Glossary sections with HowTo and FAQPage JSON-LD structured data - /fetch page: dedicated compact layout, wider result card (440px thumb, 1080px max), channel pill and duration badge on thumbnail - Redesigned GetterInput: label wrapper for click-to-focus, Paste button no longer gated behind clipboard-read permission query - Download progress: jumps to 100% when server responds, shows "Saving to your device…" phase, blob URL revoked after 1 s safety delay - Retry button shown on download error SEO - New /how-to-download-youtube-videos guide page with Article + HowTo JSON-LD - Full legal section: terms, privacy, cookies, DMCA, contact (with sidebar nav) - Sitemap extended with new routes - WebApplication JSON-LD in root layout, FAQPage on landing Library Ready / cover art - sharp installed as direct dependency; serverExternalPackages updated - Cover fetch timeout raised 5 s → 20 s (YouTube CDN can be slow) - Non-JPEG/PNG covers (WebP, AVIF…) converted to JPEG via sharp before embed - Provider cover (MusicBrainz CAA / iTunes) and YouTube thumbnail now passed separately so the thumbnail is a true fallback if the provider fetch fails
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (20)
📝 WalkthroughWalkthroughThis PR implements a comprehensive site redesign introducing a new "StroyGetter petrol-blue" design system, centralizes configuration via ChangesDesign System, Layout Refactor, Content Expansion, and Download Flow Redesign
Sequence Diagram(s)sequenceDiagram
participant User
participant GetterInput
participant VideoSelect
participant FormatTabs
participant DownloadAPI
participant EmbedID3
participant SharpReencoding
participant BlobDownload
User->>GetterInput: paste video URL
GetterInput->>GetterInput: searchQuery(url)
GetterInput->>VideoSelect: navigate with videoUrl param
User->>FormatTabs: select format (mp4/mp3/library-ready)
FormatTabs->>VideoSelect: update fmt state
VideoSelect->>DownloadAPI: POST /api/download/{fmt}
alt mp3 or library-ready
DownloadAPI->>EmbedID3: embedId3Tags with ytThumbnail fallback
EmbedID3->>SharpReencoding: fetchCoverBuffer normalizes image
SharpReencoding->>EmbedID3: return JPEG buffer with MIME type
end
DownloadAPI->>BlobDownload: return formatted audio/video blob
BlobDownload->>User: trigger download with format extension
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
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)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…s, error handling - Add /updates changelog page with slug-based routes and sitemap entries - Wire iTunes provider as intermediate cover art fallback in library-ready route - Return plain lyrics from YouTube subtitles via syltToPlain helper - Add try/catch in fetchVideoinfos with clean yt-dlp error extraction - Add .catch() in VideoSelect to surface unexpected fetch failures - Add coverFallbacks[] param to embedId3Tags for ordered cover resolution
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/layout.tsx`:
- Around line 68-88: webAppJsonLd is unnecessarily JSON.stringified then parsed
for JsonLd; change webAppJsonLd from a JSON string to a plain JS object (use the
same object literal currently inside JSON.stringify) and pass that object
directly to the JsonLd component (update any other spots that parse/stringify
the same value). Locate the webAppJsonLd definition and the JsonLd usage and
remove the stringification/JSON.parse round-trip so JsonLd receives the object
(keep siteConfig references as-is).
In `@app/legal/terms/page.tsx`:
- Around line 23-26: Update the Terms entry with n: "03" where title: "Privacy"
and its body string so it matches the Privacy page wording about analytics and
cookies; replace the current "No cookies, no third-party tracking" sentence with
a consistent statement that discloses Google Analytics 4 may set cookies (e.g.,
_ga) and that anonymous usage metrics are collected, or otherwise remove the
absolute "No cookies" claim to mirror the Privacy page’s disclosure—modify the
body field text for the object with n: "03" / title: "Privacy" accordingly.
In `@app/page.tsx`:
- Line 47: Remove the redundant explicit casts "as string | undefined" on the
badge property occurrences (the `badge` initializers in app/page.tsx referenced
in the diff) so TypeScript can infer the union type; locate the badge
declarations (the three places shown in the diff) and replace their initializers
with a simple undefined (or omit the type annotation) without the "as string |
undefined" cast.
In `@app/sitemap.ts`:
- Around line 15-38: Add a sitemap entry for the new /legal/contact route in the
sitemap array in app/sitemap.ts: mirror the existing pattern used for other
legal pages (use BASE template for the URL, set lastModified to new
Date("2026-05-07"), changeFrequency to "yearly" and an appropriate priority such
as 0.2 or 0.3) so crawlers can discover /legal/contact; locate the array of
objects (the sitemap entries around the existing objects for `/legal/terms`,
`/legal/privacy`, `/legal/cookies`, `/legal/dmca`) and insert the new object for
`/legal/contact`.
In `@components/custom/GetterInput.tsx`:
- Around line 21-30: In handlePaste, when navigator.clipboard.readText() throws,
don't silently focus the input; instead surface a brief user-visible
message—e.g., call a toast/error setter or update local state (use a provided
setError or create a short-lived message state) to show "Please paste manually
or grant clipboard permission" before focusing inputRef; keep the existing
fallback focus behavior and ensure the message clears after a few seconds or on
next input. Reference: handlePaste, navigator.clipboard.readText, inputRef,
setUrl, submitUrl.
- Line 26: Remove the artificial 100ms setTimeout call in GetterInput.tsx:
replace setTimeout(() => submitUrl(clipText), 100) by calling submitUrl directly
(await if inside an async handler) since clipText is already in scope;
alternatively use React.startTransition if you need to defer a
state-update-driven navigation. Update the handler that calls submitUrl (the
function wrapping setTimeout) to call submitUrl(clipText) directly and ensure
any async/await or transition semantics are applied there.
In `@components/custom/JsonLd.tsx`:
- Line 7: The JsonLd component currently injects raw JSON via
dangerouslySetInnerHTML using JSON.stringify(data), which can allow a
`</script>` to break out; fix it by sanitizing the JSON string before
injection—compute a safe string from JSON.stringify(data) and replace any '<'
characters (e.g., .replace(/</g, '\\u003c')) so script-breaking sequences are
neutralized, then pass that sanitized string to dangerouslySetInnerHTML; update
the JsonLd component to use this sanitized payload when referencing data and
dangerouslySetInnerHTML.
In `@components/custom/SiteHeader.tsx`:
- Line 39: The Logo image currently uses CSS filters ("brightness-0 invert")
which is fragile; replace that by either importing a dedicated white SVG (use
that asset instead of `logo`) or update the SVG file to use fill="currentColor"
and then remove the filter classes and set the Image/parent to use a text color
(e.g., add className="text-white" to the Image or its wrapper). Locate the Image
usage in SiteHeader (the <Image src={logo} ... /> line), remove the
"brightness-0 invert" classes, and update the import or SVG content accordingly
so the logo color is explicit and maintainable.
In `@components/custom/VideoSelect.tsx`:
- Around line 222-230: The mapping over formats in VideoSelect.tsx currently
returns null for entries missing qualityLabel or itag, causing empty dropdown
slots; replace the inline null-check by filtering the formats array first (e.g.,
use formats.filter(f => f.qualityLabel && f.itag) ) and then map the filtered
list to render <SelectItem> elements (keep using f.itag as the key and value and
f.qualityLabel as the label) inside SelectContent to ensure no null gaps in the
Select.
- Line 76: The code sets selectedItag from value.format[0].itag without
verifying format[0] exists; update the logic in VideoSelect.tsx (around the
setSelectedItag call) to first guard that value.format is an array and
value.format[0] and value.format[0].itag are defined (or use optional chaining
like value.format?.[0]?.itag) before calling setSelectedItag, and fall back to a
safe default (e.g., undefined or empty string) when no itag is available.
In `@lib/site-config.ts`:
- Line 10: The siteConfig.url can contain a trailing slash from
NEXT_PUBLIC_SITE_URL which breaks the no-trailing-slash contract; update how
siteConfig.url is constructed (the url property) to normalize the value by
stripping any trailing slashes from process.env.NEXT_PUBLIC_SITE_URL before
falling back to the hardcoded default (and ensure the default has no trailing
slash), e.g. trim trailing slashes with a replace or equivalent so
siteConfig.url never ends with '/'.
In `@lib/song-matching.ts`:
- Line 38: The current SEPARATOR_RE (const SEPARATOR_RE) allows mismatched
brackets because the fragment `[([].*[)\]]` accepts any opening with any
closing; update the regex to enforce matching bracket pairs by replacing that
fragment with a non-capturing alternation that matches either balanced
parentheses or balanced square brackets, e.g. use `(?:\([^)]*\)|\[[^\]]*\])` in
the SEPARATOR_RE definition so titles like "(text]" or "[text)" no longer match
incorrectly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: af0182c5-07c0-41a7-ad0f-bd51149c2600
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (30)
app/api/download/audio-library-ready/route.tsapp/api/download/audio/route.tsapp/api/download/video/route.tsapp/fetch/page.tsxapp/globals.cssapp/how-to-download-youtube-videos/page.tsxapp/layout.tsxapp/legal/contact/page.tsxapp/legal/cookies/page.tsxapp/legal/dmca/page.tsxapp/legal/layout.tsxapp/legal/privacy/page.tsxapp/legal/terms/page.tsxapp/page.tsxapp/robots.tsapp/sitemap.tscomponents/custom/GetterInput.tsxcomponents/custom/JsonLd.tsxcomponents/custom/SiteFooter.tsxcomponents/custom/SiteHeader.tsxcomponents/custom/SkeletonInput.tsxcomponents/custom/VideoEmpty.tsxcomponents/custom/VideoLoading.tsxcomponents/custom/VideoSelect.tsxcomponents/ui/accordion.tsxlib/embed-id3.tslib/site-config.tslib/song-matching.tsnext.config.tspackage.json
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Useimport { prisma } from '@/lib/prisma'for all Prisma client access in Next.js 16 with Prisma 7 — never instantiatenew PrismaClient()
Never callprisma.$disconnect()on the singleton Prisma instance, as it breaks subsequent requests and cron runs
Useyoutubei.jsvialib/innertube.tsandgetBasicInfo()for fetching metadata and format lists
Parse yt-dlp format information usinglib/ytdlp-info.tsmodule
Files:
components/custom/SkeletonInput.tsxnext.config.tsapp/legal/privacy/page.tsxcomponents/custom/SiteFooter.tsxcomponents/custom/JsonLd.tsxcomponents/custom/VideoEmpty.tsxapp/robots.tsapp/legal/layout.tsxapp/api/download/video/route.tsapp/legal/contact/page.tsxapp/legal/cookies/page.tsxapp/legal/terms/page.tsxapp/fetch/page.tsxcomponents/custom/VideoLoading.tsxlib/song-matching.tsapp/api/download/audio/route.tsapp/legal/dmca/page.tsxcomponents/custom/SiteHeader.tsxapp/how-to-download-youtube-videos/page.tsxlib/site-config.tsapp/sitemap.tscomponents/ui/accordion.tsxapp/api/download/audio-library-ready/route.tsapp/page.tsxlib/embed-id3.tscomponents/custom/GetterInput.tsxapp/layout.tsxcomponents/custom/VideoSelect.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Biome for linting via
pnpm lintcommand
Files:
components/custom/SkeletonInput.tsxnext.config.tsapp/legal/privacy/page.tsxcomponents/custom/SiteFooter.tsxcomponents/custom/JsonLd.tsxcomponents/custom/VideoEmpty.tsxapp/robots.tsapp/legal/layout.tsxapp/api/download/video/route.tsapp/legal/contact/page.tsxapp/legal/cookies/page.tsxapp/legal/terms/page.tsxapp/fetch/page.tsxcomponents/custom/VideoLoading.tsxlib/song-matching.tsapp/api/download/audio/route.tsapp/legal/dmca/page.tsxcomponents/custom/SiteHeader.tsxapp/how-to-download-youtube-videos/page.tsxlib/site-config.tsapp/sitemap.tscomponents/ui/accordion.tsxapp/api/download/audio-library-ready/route.tsapp/page.tsxlib/embed-id3.tscomponents/custom/GetterInput.tsxapp/layout.tsxcomponents/custom/VideoSelect.tsx
**/{api,functions}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
youtube-dl-exec(yt-dlp binary) for actual stream downloading viaselectYtDlpPath()function
Files:
app/api/download/video/route.tsapp/api/download/audio/route.tsapp/api/download/audio-library-ready/route.ts
**/{api,functions,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/{api,functions,lib}/**/*.{ts,tsx}: Store merged MP4s intemp/cached/directory and index them in theFiletable by URL+quality for caching
Implement temp directory structure as./temp/{source,cached}in development and/temp/stroygetter/{source,cached}in production, created automatically viainitializeConf()
Files:
app/api/download/video/route.tslib/song-matching.tsapp/api/download/audio/route.tslib/site-config.tsapp/api/download/audio-library-ready/route.tslib/embed-id3.ts
app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
app/api/**/*.{ts,tsx}: Stream audio quality directly via ffmpeg → PassThrough → Response without caching to disk
Fetch thumbnails to temp files for album art embedding, then delete after ffmpeg closes
Files:
app/api/download/video/route.tsapp/api/download/audio/route.tsapp/api/download/audio-library-ready/route.ts
package.json
📄 CodeRabbit inference engine (CLAUDE.md)
Ensure
copy-binaries.jsruns as a postbuild script to copy the yt-dlp binary into.next/standalone/for production deployment
Files:
package.json
🪛 ast-grep (0.42.1)
components/custom/JsonLd.tsx
[warning] 6-6: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 OpenGrep (1.20.0)
components/custom/JsonLd.tsx
[WARNING] 3-9: dangerouslySetInnerHTML with dynamic content can lead to XSS. Sanitize the input with a library like DOMPurify before rendering.
(coderabbit.xss.react-dangerously-set-innerhtml)
🔇 Additional comments (22)
lib/song-matching.ts (2)
53-78: LGTM! Robust fallback strategy for metadata extraction.The three-tier fallback logic (yt-dlp metadata → parsed title → raw fallback) is well-designed and handles missing or malformed data gracefully. The function correctly prioritizes structured metadata when available and falls back to heuristic parsing only when needed.
46-46: 💤 Low valueLine 46's suffix-stripping serves keyword-specific filtering, but its necessity is unclear without test coverage.
The SEPARATOR_RE removes trailing brackets from the entire title, while line 46 removes keyword-specific bracketed content (e.g., "[Official Audio]") from the extracted title. These are different concerns. However, without tests demonstrating when line 46 actually removes content from real YouTube titles, it's unclear whether this is handling legitimate edge cases or defensive code. Consider adding tests with real YouTube title samples to verify this step is needed.
components/custom/SkeletonInput.tsx (1)
5-5: Layout tweak is clean and behavior-preserving.package.json (1)
23-23: Dependency additions are consistent with the implementation and deployment setup.Also applies to: 39-39
app/api/download/audio/route.ts (1)
7-7: Import reorder is safe and behavior-neutral.app/api/download/video/route.ts (1)
8-8: Import ordering change is clean and non-breaking.next.config.ts (1)
5-5:serverExternalPackagesaddition aligns with the new runtime dependencies.components/ui/accordion.tsx (1)
1-54: Accordion composition is solid and idiomatic for Radix + React refs.lib/embed-id3.ts (2)
20-48: LGTM! Well-designed fallback and conversion logic.The 20-second timeout is appropriate given the redirect chains mentioned in the comment. The content-type detection handles multiple variations (includes/startsWith checks), and converting to JPEG at quality 92 balances file size with visual fidelity for album art. The null-return-on-error pattern enables clean fallback to the next cover candidate.
63-80: LGTM! Clean fallback implementation.The provider cover (MusicBrainz/iTunes) → YouTube thumbnail fallback chain is correctly implemented with detailed logging and early-exit on success.
app/api/download/audio-library-ready/route.ts (1)
64-81: LGTM! Correctly separates provider cover from YouTube thumbnail fallback.The change aligns with the updated
embedId3TagsAPI inlib/embed-id3.ts, allowing the embed logic to try the provider cover first and fall back to the YouTube thumbnail only if needed.components/custom/SiteHeader.tsx (1)
88-113: LGTM! Mobile menu implementation is clean.The overlay uses
absolutepositioning with properinset-x-0 top-fullandz-50to position below the header. The menu closes on navigation, and ARIA labels are present for accessibility.app/globals.css (1)
9-20: LGTM! Clean design system implementation.The new "StroyGetter petrol-blue" palette is well-structured with 11 shades,
smooth-scrollimproves UX, and the Satoshi font integration with proper antialiasing is correct. The shadcn/ui token mappings preserve component compatibility.Also applies to: 52-62, 72-100
components/custom/VideoLoading.tsx (1)
5-7: LGTM! Styling updated to match new design system.components/custom/VideoEmpty.tsx (1)
3-7: LGTM! Styling and message updated to match new design.components/custom/VideoSelect.tsx (2)
81-89: LGTM! Smooth progress simulation using exponential decay.The exponential formula
90 * (1 - Math.exp(-elapsed / 10000))provides a natural-feeling progress curve that slows as it approaches 90%, then jumps to 100% when the server responds (line 108). The 1-second safety delay before revoking the object URL (line 118) is appropriate.
91-123: LGTM! Download flow correctly handles different formats.The endpoint selection logic (lines 100-102) properly routes to the appropriate API based on format, and error handling with user feedback is well-implemented.
app/fetch/page.tsx (2)
7-10: LGTM — Clean metadata setup for non-indexed page.The
robots: { index: false }correctly prevents search engines from indexing the download results page, which is appropriate for a dynamic, user-specific interface.
12-28: LGTM — Proper async boundary pattern.Converting the page component from
asyncto synchronous while keeping async work isolated in Suspense boundaries is the correct approach. The fallback components provide appropriate loading states.app/page.tsx (1)
110-134: LGTM — Proper structured data implementation.The
HowToandFAQPageJSON-LD schemas are correctly structured according to schema.org specifications. The mapping from the static data arrays is clean and maintains semantic integrity.app/how-to-download-youtube-videos/page.tsx (1)
60-117: LGTM — Well-structured schema.org markup.The three JSON-LD blocks (Article, HowTo, FAQPage) provide comprehensive structured data for search engines. The schemas are properly nested and follow schema.org specifications.
app/layout.tsx (1)
90-102: LGTM — Clean root layout structure.The layout properly sets up global font variables, structured data, site chrome (header/footer), and analytics. The placement of
GoogleAnalyticsafter the closing</html>tag is correct for the@next/third-partiespackage.
Adds two new cover art fallback providers (Deezer, YouTube Music) to improve hit rate on well-known tracks where Cover Art Archive returns 404. Cover chain is now: MusicBrainz CAA → iTunes → Deezer → YouTube Music → YouTube thumbnail.
- layout.tsx: webAppJsonLd is now a plain object (no stringify/parse) - terms/page.tsx: disclose GA4 cookies instead of claiming "No cookies" - page.tsx: remove redundant `as string | undefined` casts on badge - sitemap.ts: add /legal/contact entry - GetterInput: drop setTimeout, surface clipboard permission error - SiteHeader: swap to logo-white.svg, drop brightness-0 invert filter - VideoSelect: filter formats before map, guard itag with optional chaining - site-config: strip trailing slash from NEXT_PUBLIC_SITE_URL - song-matching: enforce matching brackets in SEPARATOR_RE
UI / Design
SEO
Library Ready / cover art
Summary by CodeRabbit
Release Notes
New Features
Style