perf: performance optimisations#3
Conversation
- Cloudinary getOptimisedUrl() helper with context transforms - ISR revalidate=3600 on all public pages - Parallel DB queries with Promise.all() - Selective column fetching on all list pages - Full OG and Twitter metadata on all public pages - robots.ts blocking /admin and /api from crawlers - sitemap.ts exposing all public and dynamic routes - All content images using next/image
📝 WalkthroughWalkthroughThis PR adds ISR configuration and metadata exports across public pages, optimizes database queries with explicit column projections, introduces image URL optimization via ChangesISR, Metadata, Query Optimization & Image URL Enhancement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
AGENTS.md (1)
392-399: 💤 Low valueAdd language identifier to fenced code block.
The code block showing blog post layout structure lacks a language identifier, which affects syntax highlighting and linter compliance.
📝 Proposed fix
-``` +```text [Cover image — full width, 1200x630px] CATEGORY BADGE Title (JetBrains Mono, H1)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` around lines 392 - 399, The fenced code block that shows the blog post layout lacks a language identifier; update the opening triple-backtick for that block (the snippet starting with "[Cover image — full width, 1200x630px]") to include a language tag such as "text" (i.e., change ``` to ```text) so linters and syntax highlighters recognize the block.src/app/sitemap.ts (1)
17-57: ⚡ Quick winAdd
export const revalidate = 3600to cache the sitemap.Without it,
sitemap()is dynamically rendered — every crawler request (and Next.js ISR revalidation of other pages) triggers a fresh DB round-trip. All other public pages in this PR exportrevalidate = 3600; the sitemap should do the same.⚡ Proposed fix
import type { MetadataRoute } from "next"; import { desc, eq } from "drizzle-orm"; import { blogPosts, db, products } from "@/db"; + +export const revalidate = 3600; const baseUrl = "https://codeddevs.com";As per coding guidelines: "ISR — add to all public pages:
export const revalidate = 3600".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/sitemap.ts` around lines 17 - 57, Add a top-level export for ISR by declaring export const revalidate = 3600 in this module so the sitemap() function is cached for 1 hour; update src/app/sitemap.ts to include the export alongside the existing default export (sitemap) to prevent dynamic rendering and avoid repeated DB queries on each crawler request.src/lib/cloudinary.ts (1)
36-36: ⚡ Quick win
getOptimisedUrlapplied at upload time bakes transformation strings into the DB — prefer applying it only at render time.
resolve(getOptimisedUrl(result.secure_url))stores…/upload/f_auto,q_auto/…in the database. Every render-side caller already appliesgetOptimisedUrlindependently, so the DB write is redundant. The bigger concern is forward compatibility: if you later want to adddpr_autoor swap quality settings, every stored URL would require a data migration rather than a one-line code change.Returning the raw
secure_urlhere and relying solely on render-time transformation keeps the DB agnostic to Cloudinary delivery parameters.♻️ Proposed fix
- resolve(getOptimisedUrl(result.secure_url)); + resolve(result.secure_url);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/cloudinary.ts` at line 36, The code currently calls getOptimisedUrl(result.secure_url) before resolving the upload promise, which bakes Cloudinary transformation params into stored URLs; change the upload flow so the promise resolves with the raw result.secure_url instead (i.e., resolve(result.secure_url) in the upload handler) and remove any stored application of getOptimisedUrl in the upload path so that render-time callers can apply getOptimisedUrl when needed; update any references to the upload promise return value accordingly if they assumed transformed URLs.src/app/(public)/products/[slug]/page.tsx (1)
38-50: ⚡ Quick winDeduplicate the DB call shared between
generateMetadataandProductPageusingReact.cache().Same pattern as
src/app/(public)/blog/[slug]/page.tsx:getProductBySlugis invoked in bothgenerateMetadata(line 72) andProductPage(line 108), resulting in two identical Drizzle queries per render. To deduplicate database calls, extract the function and wrap it withReact.cache()— this applies equally to thegenerateMetadata/ page component pair.♻️ Proposed fix
+import { cache } from "react"; ... -async function getProductBySlug(slug: string) { +const getProductBySlug = cache(async (slug: string) => { try { const [product] = await db .select() .from(products) .where(eq(products.slug, slug)) .limit(1); return product ?? null; } catch { return null; } -} +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`(public)/products/[slug]/page.tsx around lines 38 - 50, The getProductBySlug function is called twice (in generateMetadata and ProductPage) causing duplicate DB queries — wrap this function with React.cache and use the cached version in both generateMetadata and ProductPage to dedupe calls; specifically, import React if needed, create a cached function (e.g., const getProductBySlugCached = React.cache(async function getProductBySlug(...){...})) or wrap the existing getProductBySlug with React.cache, keep the same signature and error handling, and replace both usages (generateMetadata and ProductPage) to call the cached function.src/app/(public)/blog/[slug]/page.tsx (1)
36-48: ⚡ Quick winDeduplicate the DB call shared between
generateMetadataandUpdatePageusingReact.cache().
getPublishedPostBySlugis called twice per render pass — once ingenerateMetadata(line 71) and again inUpdatePage(line 107). Because this uses Drizzle (notfetch), automatic deduplication does not apply to ORM/database calls; Reactcachecan be used iffetchis unavailable. Without this, every ISR regeneration or on-demand render fires two identical queries.♻️ Proposed fix
+import { cache } from "react"; ... -async function getPublishedPostBySlug(slug: string) { +const getPublishedPostBySlug = cache(async (slug: string) => { try { const [post] = await db .select() .from(blogPosts) .where(and(eq(blogPosts.slug, slug), eq(blogPosts.is_published, true))) .limit(1); return post ?? null; } catch { return null; } -} +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`(public)/blog/[slug]/page.tsx around lines 36 - 48, Wrap the existing getPublishedPostBySlug function with React.cache so the same DB query is deduplicated between generateMetadata and UpdatePage; import React if needed and replace/export the cached version (e.g. const getPublishedPostBySlug = React.cache(async function getPublishedPostBySlug(slug: string) { ... })) so both generateMetadata and UpdatePage call the cached function without changing call sites, leaving the internal Drizzle query unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`(public)/blog/[slug]/page.tsx:
- Line 10: Replace the direct import of getOptimisedUrl from
"@/lib/cloudinary-url" with the canonical re-export from "@/lib/cloudinary";
locate the import statement that currently imports getOptimisedUrl in the Blog
page component (page.tsx) and update its source to "@/lib/cloudinary" so the
component uses the vetted helper re-export.
In `@src/app/`(public)/contact/page.tsx:
- Around line 1-6: This page is missing the ISR revalidate export; add an
exported constant `revalidate` set to 3600 in this module (page.tsx) alongside
the existing top-level symbols such as `title`, `description`, and the
`ContactForm` import so the page uses 1-hour incremental static regeneration
(export const revalidate = 3600).
In `@src/app/`(public)/products/[slug]/page.tsx:
- Line 8: Replace the direct import of getOptimisedUrl from
"@/lib/cloudinary-url" with the sanctioned helper from "@/lib/cloudinary":
update the import statement that currently references getOptimisedUrl to import
it from "@/lib/cloudinary" instead, ensuring any usages of getOptimisedUrl in
this file (page component for product slug) remain unchanged and continue to
resolve correctly against the new module.
In `@src/app/`(public)/team/page.tsx:
- Line 6: The import is pulling getOptimisedUrl from "@/lib/cloudinary-url"
instead of the canonical helper; update the import in
src/app/(public)/team/page.tsx to import getOptimisedUrl from "@/lib/cloudinary"
(replace the existing import statement that references "@/lib/cloudinary-url")
so the component uses the shared helper function getOptimisedUrl from
cloudinary.ts.
In `@src/components/admin/ImageUpload.tsx`:
- Line 6: Update the import in ImageUpload.tsx to use the centralized helper:
replace the current import of getOptimisedUrl from "@/lib/cloudinary-url" with
an import from "@/lib/cloudinary" so the component uses the canonical
getOptimisedUrl helper; locate the import statement that references
getOptimisedUrl and change its module specifier accordingly (no other code
changes needed).
---
Nitpick comments:
In `@AGENTS.md`:
- Around line 392-399: The fenced code block that shows the blog post layout
lacks a language identifier; update the opening triple-backtick for that block
(the snippet starting with "[Cover image — full width, 1200x630px]") to include
a language tag such as "text" (i.e., change ``` to ```text) so linters and
syntax highlighters recognize the block.
In `@src/app/`(public)/blog/[slug]/page.tsx:
- Around line 36-48: Wrap the existing getPublishedPostBySlug function with
React.cache so the same DB query is deduplicated between generateMetadata and
UpdatePage; import React if needed and replace/export the cached version (e.g.
const getPublishedPostBySlug = React.cache(async function
getPublishedPostBySlug(slug: string) { ... })) so both generateMetadata and
UpdatePage call the cached function without changing call sites, leaving the
internal Drizzle query unchanged.
In `@src/app/`(public)/products/[slug]/page.tsx:
- Around line 38-50: The getProductBySlug function is called twice (in
generateMetadata and ProductPage) causing duplicate DB queries — wrap this
function with React.cache and use the cached version in both generateMetadata
and ProductPage to dedupe calls; specifically, import React if needed, create a
cached function (e.g., const getProductBySlugCached = React.cache(async function
getProductBySlug(...){...})) or wrap the existing getProductBySlug with
React.cache, keep the same signature and error handling, and replace both usages
(generateMetadata and ProductPage) to call the cached function.
In `@src/app/sitemap.ts`:
- Around line 17-57: Add a top-level export for ISR by declaring export const
revalidate = 3600 in this module so the sitemap() function is cached for 1 hour;
update src/app/sitemap.ts to include the export alongside the existing default
export (sitemap) to prevent dynamic rendering and avoid repeated DB queries on
each crawler request.
In `@src/lib/cloudinary.ts`:
- Line 36: The code currently calls getOptimisedUrl(result.secure_url) before
resolving the upload promise, which bakes Cloudinary transformation params into
stored URLs; change the upload flow so the promise resolves with the raw
result.secure_url instead (i.e., resolve(result.secure_url) in the upload
handler) and remove any stored application of getOptimisedUrl in the upload path
so that render-time callers can apply getOptimisedUrl when needed; update any
references to the upload promise return value accordingly if they assumed
transformed URLs.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 15cc8d01-5583-4a9f-877a-00d2203dbc44
📒 Files selected for processing (24)
AGENTS.mdCLAUDE.mdsrc/app/(public)/about/page.tsxsrc/app/(public)/blog/[slug]/page.tsxsrc/app/(public)/blog/page.tsxsrc/app/(public)/careers/page.tsxsrc/app/(public)/contact/page.tsxsrc/app/(public)/page.tsxsrc/app/(public)/products/[slug]/page.tsxsrc/app/(public)/products/page.tsxsrc/app/(public)/team/page.tsxsrc/app/admin/blog/page.tsxsrc/app/admin/careers/page.tsxsrc/app/admin/dashboard/page.tsxsrc/app/admin/products/page.tsxsrc/app/admin/team/page.tsxsrc/app/robots.tssrc/app/sitemap.tssrc/components/admin/ImageUpload.tsxsrc/components/blog/UpdatesList.tsxsrc/components/sections/LatestReleasesSection.tsxsrc/components/sections/ProductsSection.tsxsrc/lib/cloudinary-url.tssrc/lib/cloudinary.ts
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/lib/cloudinary.ts (1)
21-31: 💤 Low valueConsider splitting
getOptimisedUrlinto its own module instead of usingnew Functionfor bundler isolation.
new Function("return import('cloudinary')")correctly prevents webpack from statically analysing the cloudinary import (so it isn't bundled into server-component chunks that only needgetOptimisedUrl). However, movinggetOptimisedUrl— a zero-dependency pure string function — to a separate file (e.g.src/lib/cloudinary-url.ts) would achieve the same goal more cleanly, allowuploadToCloudinaryto use a directimport { v2 as cloudinary } from 'cloudinary', and remove the need for the type cast.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/cloudinary.ts` around lines 21 - 31, The current use of new Function("return import('cloudinary')") to avoid bundling should be replaced by moving the pure string helper getOptimisedUrl into its own module so cloudinary can be imported normally; create a new file (e.g., export getOptimisedUrl from src/lib/cloudinary-url.ts) and update call sites to import that helper directly, then in the uploadToCloudinary function import { v2 as cloudinary } from 'cloudinary' and remove the new Function wrapper and the type cast, keeping cloudinary.config(...) and publicId computation as-is and updating any references to getOptimisedUrl to the new module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@AGENTS.md`:
- Around line 299-302: Add a blank line immediately before the "### Admin (401
if no session)" heading's table so the Markdown table starting with the row "|
Method | Route | Description |" is surrounded by a blank line to satisfy MD058;
locate the "### Admin (401 if no session)" heading and insert one empty line
above the table that lists POST `/api/upload`.
- Around line 444-458: Documentation inaccurately states getOptimisedUrl(appends
context-specific transforms) but the implementation in src/lib/cloudinary.ts
only inserts f_auto/q_auto and its signature is (url: string | null | undefined)
with no context; either update AGENTS.md to reflect current behavior or change
getOptimisedUrl to accept an optional context parameter and apply the right
transforms. To fix in code: modify getOptimisedUrl to (url: string | null |
undefined, context?: string | undefined), add a small mapping of context keys
(e.g., "blogCover","recognitionCard","blogThumbnail","teamPhoto","productCover")
to transform strings using slash-separated format (e.g.,
"f_auto/q_auto/w_1200,h_630,c_fill" and include "g_face" for teamPhoto), then
inject the chosen transform into Cloudinary delivery URLs where getOptimisedUrl
currently injects f_auto/q_auto; alternatively, if you choose the documentation
route, update AGENTS.md to state the helper only inserts f_auto/q_auto (use
slash-separated syntax) and mark context-specific transforms as not yet applied.
In `@src/lib/cloudinary.ts`:
- Around line 1-15: getOptimisedUrl currently only applies f_auto,q_auto and
lacks context-specific transforms; modify getOptimisedUrl to accept an optional
context parameter (e.g., "team-photo", "blog-cover", "product-cover"), map each
context to the Cloudinary transformation strings documented in AGENTS.md
(team-photo -> f_auto,q_auto,w_400,h_400,c_fill,g_face; blog-cover/product-cover
-> f_auto,q_auto,w_1200,h_630,c_fill), apply the full transform when replacing
"/upload/" (while preserving existing early-return checks for non-Cloudinary
URLs and already-optimised URLs), and update all call sites (e.g.,
team/page.tsx, blog/[slug]/page.tsx) to pass the appropriate context so images
receive the correct crop/size/gravity.
- Line 14: The current getOptimisedUrl function returns url.replace("/upload/",
"/upload/f_auto,q_auto/") which uses invalid Cloudinary syntax and lacks
context-specific transforms; update getOptimisedUrl to accept a context
parameter (e.g., "blogCover" | "productCover" | "teamPhoto"), replace the comma
with a slash so the base optimization is "/upload/f_auto/q_auto/", and append
context-specific transformation strings per AGENTS.md (for example for
blog/product covers add "w_1200,h_630,c_fill" and for team photos add
"w_400,h_400,c_fill,g_face"); ensure the function composes the final replacement
segment (e.g., "/upload/{transformations}/f_auto/q_auto/") and update any
callers of getOptimisedUrl to pass the appropriate context.
---
Nitpick comments:
In `@src/lib/cloudinary.ts`:
- Around line 21-31: The current use of new Function("return
import('cloudinary')") to avoid bundling should be replaced by moving the pure
string helper getOptimisedUrl into its own module so cloudinary can be imported
normally; create a new file (e.g., export getOptimisedUrl from
src/lib/cloudinary-url.ts) and update call sites to import that helper directly,
then in the uploadToCloudinary function import { v2 as cloudinary } from
'cloudinary' and remove the new Function wrapper and the type cast, keeping
cloudinary.config(...) and publicId computation as-is and updating any
references to getOptimisedUrl to the new module.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 63783876-3a98-435c-b5c4-23a95bbb6fb8
📒 Files selected for processing (8)
AGENTS.mdsrc/app/(public)/blog/[slug]/page.tsxsrc/app/(public)/contact/page.tsxsrc/app/(public)/products/[slug]/page.tsxsrc/app/(public)/team/page.tsxsrc/app/sitemap.tssrc/components/admin/ImageUpload.tsxsrc/lib/cloudinary.ts
✅ Files skipped from review due to trivial changes (1)
- src/components/admin/ImageUpload.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/app/sitemap.ts
- src/app/(public)/products/[slug]/page.tsx
| ### Admin (401 if no session) | ||
| | Method | Route | Description | | ||
| |---|---|---| | ||
| | POST | `/api/upload` | Upload image to Cloudinary | | ||
| | POST | `/api/upload` | Upload to Cloudinary | |
There was a problem hiding this comment.
Add a blank line before the Admin table to satisfy MD058.
The static analysis tool (markdownlint-cli2) flags this table as missing surrounding blank lines, which can cause some Markdown renderers to fail to parse it as a table.
✏️ Proposed fix
### Admin (401 if no session)
+
| Method | Route | Description |📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Admin (401 if no session) | |
| | Method | Route | Description | | |
| |---|---|---| | |
| | POST | `/api/upload` | Upload image to Cloudinary | | |
| | POST | `/api/upload` | Upload to Cloudinary | | |
| ### Admin (401 if no session) | |
| | Method | Route | Description | | |
| |---|---|---| | |
| | POST | `/api/upload` | Upload to Cloudinary | |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 300-300: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@AGENTS.md` around lines 299 - 302, Add a blank line immediately before the
"### Admin (401 if no session)" heading's table so the Markdown table starting
with the row "| Method | Route | Description |" is surrounded by a blank line to
satisfy MD058; locate the "### Admin (401 if no session)" heading and insert one
empty line above the table that lists POST `/api/upload`.
| ### Cloudinary URL transformations | ||
| The `getOptimisedUrl()` helper in `src/lib/cloudinary.ts` appends | ||
| transformations automatically. Never use raw Cloudinary URLs directly. | ||
|
|
||
| ```ts | ||
| // Context-specific transformations: | ||
| Blog cover banner: f_auto,q_auto,w_1200,h_630,c_fill | ||
| Recognition card: f_auto,q_auto,w_600,h_315,c_fill | ||
| Blog list thumbnail: f_auto,q_auto,w_800,h_420,c_fill | ||
| Team photo: f_auto,q_auto,w_400,h_400,c_fill,g_face | ||
| Product cover: f_auto,q_auto,w_1200,h_630,c_fill | ||
| ``` | ||
|
|
||
| `g_face` on team photos tells Cloudinary to focus the crop on the face. | ||
|
|
There was a problem hiding this comment.
Documentation claims context-specific transforms are applied by getOptimisedUrl, but the implementation doesn't support them.
The section states the helper "appends transformations automatically" and shows context-specific transform strings (w_1200,h_630,c_fill, w_400,h_400,c_fill,g_face, etc.), implying the function selects transforms based on call-site context. The actual getOptimisedUrl signature is (url: string | null | undefined): string — no context parameter — and it inserts only f_auto,q_auto regardless of where it's called.
Additionally, the transform strings shown throughout this block use the comma-separated form f_auto,q_auto, which is the same invalid syntax flagged in src/lib/cloudinary.ts.
This documentation should be corrected to accurately describe what the helper currently does, or updated in sync with the context-parameter fix proposed for cloudinary.ts.
✏️ Corrected documentation (minimal fix, no implementation change)
-### Cloudinary URL transformations
-The `getOptimisedUrl()` helper in `src/lib/cloudinary.ts` appends
-transformations automatically. Never use raw Cloudinary URLs directly.
-
-```ts
-// Context-specific transformations:
-Blog cover banner: f_auto,q_auto,w_1200,h_630,c_fill
-Recognition card: f_auto,q_auto,w_600,h_315,c_fill
-Blog list thumbnail: f_auto,q_auto,w_800,h_420,c_fill
-Team photo: f_auto,q_auto,w_400,h_400,c_fill,g_face
-Product cover: f_auto,q_auto,w_1200,h_630,c_fill
-```
+### Cloudinary URL transformations
+The `getOptimisedUrl()` helper in `src/lib/cloudinary.ts` inserts `f_auto/q_auto`
+into Cloudinary delivery URLs. Never use raw Cloudinary URLs directly.
+
+Context-specific dimension and crop transforms (`w_`, `h_`, `c_fill`, `g_face`)
+are **not yet applied** — tracked for a follow-up.
+
+Target transforms per context (implement via an optional `context` parameter):
+```ts
+Blog cover banner: f_auto/q_auto/w_1200,h_630,c_fill
+Recognition card: f_auto/q_auto/w_600,h_315,c_fill
+Blog list thumbnail: f_auto/q_auto/w_800,h_420,c_fill
+Team photo: f_auto/q_auto/w_400,h_400,c_fill,g_face
+Product cover: f_auto/q_auto/w_1200,h_630,c_fill
+```🧰 Tools
🪛 LanguageTool
[grammar] ~445-~445: Ensure spelling is correct
Context: ... ### Cloudinary URL transformations The getOptimisedUrl() helper in src/lib/cloudinary.ts ap...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@AGENTS.md` around lines 444 - 458, Documentation inaccurately states
getOptimisedUrl(appends context-specific transforms) but the implementation in
src/lib/cloudinary.ts only inserts f_auto/q_auto and its signature is (url:
string | null | undefined) with no context; either update AGENTS.md to reflect
current behavior or change getOptimisedUrl to accept an optional context
parameter and apply the right transforms. To fix in code: modify getOptimisedUrl
to (url: string | null | undefined, context?: string | undefined), add a small
mapping of context keys (e.g.,
"blogCover","recognitionCard","blogThumbnail","teamPhoto","productCover") to
transform strings using slash-separated format (e.g.,
"f_auto/q_auto/w_1200,h_630,c_fill" and include "g_face" for teamPhoto), then
inject the chosen transform into Cloudinary delivery URLs where getOptimisedUrl
currently injects f_auto/q_auto; alternatively, if you choose the documentation
route, update AGENTS.md to state the helper only inserts f_auto/q_auto (use
slash-separated syntax) and mark context-specific transforms as not yet applied.
| export function getOptimisedUrl(url: string | null | undefined): string { | ||
| if (!url) { | ||
| return ""; | ||
| } | ||
|
|
||
| cloudinary.config({ | ||
| cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, | ||
| api_key: process.env.CLOUDINARY_API_KEY, | ||
| api_secret: process.env.CLOUDINARY_API_SECRET, | ||
| }); | ||
| if (!url.includes("res.cloudinary.com") || !url.includes("/upload/")) { | ||
| return url; | ||
| } | ||
|
|
||
| if (url.includes("/upload/f_auto,q_auto/")) { | ||
| return url; | ||
| } | ||
|
|
||
| return url.replace("/upload/", "/upload/f_auto,q_auto/"); | ||
| } |
There was a problem hiding this comment.
getOptimisedUrl applies only f_auto/q_auto — context-specific dimension and crop transforms documented in AGENTS.md are not implemented.
AGENTS.md (updated in this same PR, lines 448–455) documents that the helper applies context-specific transforms:
Team photo: f_auto,q_auto,w_400,h_400,c_fill,g_face
Blog cover: f_auto,q_auto,w_1200,h_630,c_fill
Product cover: f_auto,q_auto,w_1200,h_630,c_fill
The current implementation ignores all dimension/crop/gravity parameters. Concretely:
- Team photos will not get face-focused cropping (
g_face) — profile photos may be cropped at arbitrary positions. - All images are served at original resolution without a width/height constraint from Cloudinary's CDN layer (Next.js Image handles display-side resizing, but the bytes transferred from Cloudinary to Vercel are unoptimised).
The function signature accepts no context parameter, so callers have no mechanism to request context-specific transforms.
🛠️ Proposed fix — add an optional context parameter
+type CloudinaryContext =
+ | "blog-cover"
+ | "blog-thumbnail"
+ | "recognition"
+ | "team-photo"
+ | "product-cover";
+
+const contextTransforms: Record<CloudinaryContext, string> = {
+ "blog-cover": "f_auto/q_auto/w_1200,h_630,c_fill",
+ "blog-thumbnail": "f_auto/q_auto/w_800,h_420,c_fill",
+ "recognition": "f_auto/q_auto/w_600,h_315,c_fill",
+ "team-photo": "f_auto/q_auto/w_400,h_400,c_fill,g_face",
+ "product-cover": "f_auto/q_auto/w_1200,h_630,c_fill",
+};
+
-export function getOptimisedUrl(url: string | null | undefined): string {
+export function getOptimisedUrl(
+ url: string | null | undefined,
+ context?: CloudinaryContext,
+): string {
if (!url) {
return "";
}
if (!url.includes("res.cloudinary.com") || !url.includes("/upload/")) {
return url;
}
+ const transforms = context ? contextTransforms[context] : "f_auto/q_auto";
- if (url.includes("/upload/f_auto,q_auto/")) {
+ if (url.includes(`/upload/${transforms}/`)) {
return url;
}
- return url.replace("/upload/", "/upload/f_auto,q_auto/");
+ return url.replace("/upload/", `/upload/${transforms}/`);
}All call sites would then pass context:
// team/page.tsx
src={getOptimisedUrl(member.photo_url, "team-photo")}
// blog/[slug]/page.tsx
src={getOptimisedUrl(post.cover_url, "blog-cover")}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/cloudinary.ts` around lines 1 - 15, getOptimisedUrl currently only
applies f_auto,q_auto and lacks context-specific transforms; modify
getOptimisedUrl to accept an optional context parameter (e.g., "team-photo",
"blog-cover", "product-cover"), map each context to the Cloudinary
transformation strings documented in AGENTS.md (team-photo ->
f_auto,q_auto,w_400,h_400,c_fill,g_face; blog-cover/product-cover ->
f_auto,q_auto,w_1200,h_630,c_fill), apply the full transform when replacing
"/upload/" (while preserving existing early-return checks for non-Cloudinary
URLs and already-optimised URLs), and update all call sites (e.g.,
team/page.tsx, blog/[slug]/page.tsx) to pass the appropriate context so images
receive the correct crop/size/gravity.
| return url; | ||
| } | ||
|
|
||
| return url.replace("/upload/", "/upload/f_auto,q_auto/"); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Cloudinary transformation rules: is "f_auto,q_auto" (comma-separated) valid in a single transformation component, or must format and quality always be in separate slash-separated components?
💡 Result:
“f_auto,q_auto” is not the correct syntax for Cloudinary’s single transformation component when you want automatic format and automatic quality together. Cloudinary’s transformation URL grammar is: parameters within a single component are comma-separated, and chained transformation components are separated by forward slashes. For automatic optimization, Cloudinary explicitly requires format and quality to be in separate slash-separated components: use “f_auto/q_auto” (correct) and “f_auto,q_auto” (incorrect). Example (correct): /f_auto/q_auto Example (incorrect): /f_auto,q_auto
Citations:
- 1: https://cloudinary.com/documentation/cloudinary_transformation_rules
- 2: https://cloudinary.com/documentation/image_transformation_reference
- 3: https://cloudinary.com/documentation/image_transformations
- 4: https://cloudinary.com/documentation/media_optimizer_transformations
🏁 Script executed:
# Check the cloudinary.ts file
cat -n src/lib/cloudinary.tsRepository: coded-devs/website
Length of output: 1874
🏁 Script executed:
# Find all usages of getOptimisedUrl across the codebase
rg "getOptimisedUrl" --type ts --type tsx -A 2 -B 2Repository: coded-devs/website
Length of output: 90
🏁 Script executed:
# Check if AGENTS.md exists and what it says about transforms
fd -i agents.mdRepository: coded-devs/website
Length of output: 72
🏁 Script executed:
# Try searching for getOptimisedUrl usage with different approach
rg "getOptimisedUrl" -A 2 -B 2Repository: coded-devs/website
Length of output: 4515
🏁 Script executed:
# Check AGENTS.md for context about transforms
head -500 AGENTS.md | tail -100Repository: coded-devs/website
Length of output: 4147
🏁 Script executed:
# Or search for the specific section mentioned in original comment (lines 448-457)
sed -n '440,460p' AGENTS.mdRepository: coded-devs/website
Length of output: 904
f_auto,q_auto comma-separated is invalid Cloudinary syntax — must be f_auto/q_auto.
Cloudinary's official documentation explicitly requires format and quality to be in separate slash-separated components: use f_auto/q_auto, never f_auto,q_auto. Comma-separated syntax causes the quality parameter to be treated as a qualifier to format rather than as a standalone optimization, silently delivering unoptimized images.
This affects all image renders: blog covers, team photos, and product covers across the site.
Additionally, getOptimisedUrl() implements only generic f_auto,q_auto but AGENTS.md documents context-specific transformations (dimensions, crop gravity, etc.) that should be applied per context:
- Blog/product covers:
w_1200,h_630,c_fill - Team photos:
w_400,h_400,c_fill,g_face(face-focused crop)
The function accepts no context parameter, so these transformations are never applied. Images are served at original resolution without context-specific sizing.
Fix required
- Change syntax from comma to slash:
- if (url.includes("/upload/f_auto,q_auto/")) {
+ if (url.includes("/upload/f_auto/q_auto/")) {
return url;
}
- return url.replace("/upload/", "/upload/f_auto,q_auto/");
+ return url.replace("/upload/", "/upload/f_auto/q_auto/");- Extend
getOptimisedUrl()to accept context parameter and apply documented context-specific transforms.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/cloudinary.ts` at line 14, The current getOptimisedUrl function
returns url.replace("/upload/", "/upload/f_auto,q_auto/") which uses invalid
Cloudinary syntax and lacks context-specific transforms; update getOptimisedUrl
to accept a context parameter (e.g., "blogCover" | "productCover" |
"teamPhoto"), replace the comma with a slash so the base optimization is
"/upload/f_auto/q_auto/", and append context-specific transformation strings per
AGENTS.md (for example for blog/product covers add "w_1200,h_630,c_fill" and for
team photos add "w_400,h_400,c_fill,g_face"); ensure the function composes the
final replacement segment (e.g., "/upload/{transformations}/f_auto/q_auto/") and
update any callers of getOptimisedUrl to pass the appropriate context.
What does this PR do?
Type of change
Checklist
Summary by CodeRabbit
Release Notes
New Features
Performance