Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nextjs-starter-template
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Moorthy G
nextjs-starter-template
Commits
4266de27
Commit
4266de27
authored
Jan 06, 2026
by
Moorthy G
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor(responsive-image): handle the image path to simplfy with '/images' or '/images/static'
parent
7655d6e5
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
208 additions
and
78 deletions
+208
-78
.env.sample
.env.sample
+1
-0
ResponsiveImage.stories.tsx
...onents/shared/ResponsiveImage/ResponsiveImage.stories.tsx
+2
-2
ResponsiveImage.tsx
components/shared/ResponsiveImage/ResponsiveImage.tsx
+22
-6
Source.tsx
components/shared/ResponsiveImage/Source.tsx
+11
-5
breakpoints.ts
components/shared/ResponsiveImage/breakpoints.ts
+1
-1
imageLoader.ts
components/shared/ResponsiveImage/imageLoader.ts
+49
-0
index.ts
components/shared/ResponsiveImage/index.ts
+1
-0
processSizes.ts
components/shared/ResponsiveImage/processSizes.ts
+4
-4
processSrcSet.ts
components/shared/ResponsiveImage/processSrcSet.ts
+74
-28
ResponsiveVideo.tsx
components/shared/ResponsiveVideo/ResponsiveVideo.tsx
+27
-27
breakpoints.ts
components/shared/ResponsiveVideo/breakpoints.ts
+1
-1
next.config.mjs
next.config.mjs
+15
-4
No files found.
.env.sample
View file @
4266de27
NEXT_PUBLIC_API_URL=http://localhost/api
NEXT_PUBLIC_API_URL=http://localhost/api
APP_ASSETS_URL=http://localhost/assets
components/shared/ResponsiveImage/ResponsiveImage.stories.tsx
View file @
4266de27
...
@@ -29,11 +29,11 @@ export const ArtDirection = {
...
@@ -29,11 +29,11 @@ export const ArtDirection = {
render
:
(
args
:
ResponsiveImageProps
)
=>
(
render
:
(
args
:
ResponsiveImageProps
)
=>
(
<
ResponsiveImage
{
...
args
}
>
<
ResponsiveImage
{
...
args
}
>
<
Source
<
Source
media=
"
x
xl"
media=
"
2
xl"
src=
"https://picsum.photos/1920/540?jpg"
src=
"https://picsum.photos/1920/540?jpg"
width=
{
1920
}
width=
{
1920
}
height=
{
540
}
height=
{
540
}
sizes=
"
x
xl 100vw"
sizes=
"
2
xl 100vw"
/>
/>
<
Source
<
Source
media=
"xl"
media=
"xl"
...
...
components/shared/ResponsiveImage/ResponsiveImage.tsx
View file @
4266de27
...
@@ -2,6 +2,7 @@ import { forwardRef } from 'react';
...
@@ -2,6 +2,7 @@ import { forwardRef } from 'react';
import
{
getImageProps
}
from
'next/image'
;
import
{
getImageProps
}
from
'next/image'
;
import
{
processSizes
}
from
'./processSizes'
;
import
{
processSizes
}
from
'./processSizes'
;
import
{
processSrcSet
}
from
'./processSrcSet'
;
import
{
processSrcSet
}
from
'./processSrcSet'
;
import
{
imageLoader
,
shouldOptimizeImage
}
from
'./imageLoader'
;
/**
/**
* ResponsiveImage component for displaying optimized images with responsive srcSet
* ResponsiveImage component for displaying optimized images with responsive srcSet
...
@@ -15,6 +16,16 @@ import { processSrcSet } from './processSrcSet';
...
@@ -15,6 +16,16 @@ import { processSrcSet } from './processSrcSet';
* src="/image.jpg"
* src="/image.jpg"
* width={1920}
* width={1920}
* height={1080}
* height={1080}
* alt="Description"
* />
* ```
*
* @example With sizes attribute
* ```tsx
* <ResponsiveImage
* src="/image.jpg"
* width={1920}
* height={1080}
* sizes="100vw"
* sizes="100vw"
* alt="Description"
* alt="Description"
* />
* />
...
@@ -33,19 +44,21 @@ import { processSrcSet } from './processSrcSet';
...
@@ -33,19 +44,21 @@ import { processSrcSet } from './processSrcSet';
* </ResponsiveImage>
* </ResponsiveImage>
* ```
* ```
*/
*/
export
interface
ResponsiveImageProps
export
interface
ResponsiveImageProps
extends
React
.
HTMLAttributes
<
HTMLImageElement
>
{
extends
React
.
HTMLAttributes
<
HTMLImageElement
>
{
src
:
string
;
src
:
string
;
width
:
number
;
width
:
number
;
height
:
number
;
height
:
number
;
sizes
:
string
;
sizes
?
:
string
;
alt
?:
string
;
alt
?:
string
;
priority
?:
boolean
;
priority
?:
boolean
;
quality
?:
number
;
quality
?:
number
;
children
?:
React
.
ReactNode
;
children
?:
React
.
ReactNode
;
}
}
export
const
ResponsiveImage
=
forwardRef
<
HTMLImageElement
,
ResponsiveImageProps
>
(
export
const
ResponsiveImage
=
forwardRef
<
HTMLImageElement
,
ResponsiveImageProps
>
(
(
(
{
{
src
,
src
,
...
@@ -64,15 +77,18 @@ export const ResponsiveImage = forwardRef<HTMLImageElement, ResponsiveImageProps
...
@@ -64,15 +77,18 @@ export const ResponsiveImage = forwardRef<HTMLImageElement, ResponsiveImageProps
return
null
;
return
null
;
}
}
const
shouldUnoptimize
=
!
shouldOptimizeImage
(
src
);
const
processedSizes
=
processSizes
(
sizes
);
const
processedSizes
=
processSizes
(
sizes
);
const
{
props
}
=
getImageProps
({
const
{
props
}
=
getImageProps
({
src
,
src
,
width
,
width
,
height
,
height
,
sizes
:
processedSizes
,
quality
,
quality
,
priority
,
priority
,
alt
alt
,
sizes
:
processedSizes
,
loader
:
imageLoader
,
...(
shouldUnoptimize
&&
{
unoptimized
:
true
})
});
});
const
filteredSrcSet
=
processSrcSet
(
props
.
srcSet
,
width
);
const
filteredSrcSet
=
processSrcSet
(
props
.
srcSet
,
width
);
...
...
components/shared/ResponsiveImage/Source.tsx
View file @
4266de27
...
@@ -3,13 +3,14 @@ import { getImageProps } from 'next/image';
...
@@ -3,13 +3,14 @@ import { getImageProps } from 'next/image';
import
breakpoints
,
{
type
BreakpointKey
}
from
'./breakpoints'
;
import
breakpoints
,
{
type
BreakpointKey
}
from
'./breakpoints'
;
import
{
processSizes
}
from
'./processSizes'
;
import
{
processSizes
}
from
'./processSizes'
;
import
{
processSrcSet
}
from
'./processSrcSet'
;
import
{
processSrcSet
}
from
'./processSrcSet'
;
import
{
imageLoader
,
shouldOptimizeImage
}
from
'./imageLoader'
;
export
interface
SourceProps
extends
React
.
HTMLAttributes
<
HTMLSourceElement
>
{
export
interface
SourceProps
extends
React
.
HTMLAttributes
<
HTMLSourceElement
>
{
src
:
string
;
src
:
string
;
width
:
number
;
width
:
number
;
height
:
number
;
height
:
number
;
media
:
string
;
media
:
string
;
sizes
:
string
;
sizes
?
:
string
;
quality
?:
number
;
quality
?:
number
;
}
}
...
@@ -22,7 +23,7 @@ export interface SourceProps extends React.HTMLAttributes<HTMLSourceElement> {
...
@@ -22,7 +23,7 @@ export interface SourceProps extends React.HTMLAttributes<HTMLSourceElement> {
*
*
* @example
* @example
* ```tsx
* ```tsx
* <ResponsiveImage src="/default.jpg" width={576} height={540}
sizes="100vw"
>
* <ResponsiveImage src="/default.jpg" width={576} height={540}>
* <Source
* <Source
* media="lg"
* media="lg"
* src="/large.jpg"
* src="/large.jpg"
...
@@ -57,15 +58,20 @@ export const Source = memo(
...
@@ -57,15 +58,20 @@ export const Source = memo(
return
null
;
return
null
;
}
}
const
query
=
(
media
in
breakpoints
?
breakpoints
[
media
as
BreakpointKey
]
:
null
)
||
media
;
const
shouldUnoptimize
=
!
shouldOptimizeImage
(
src
);
const
query
=
(
media
in
breakpoints
?
breakpoints
[
media
as
BreakpointKey
]
:
null
)
||
media
;
const
processedSizes
=
processSizes
(
sizes
);
const
processedSizes
=
processSizes
(
sizes
);
const
{
props
}
=
getImageProps
({
const
{
props
}
=
getImageProps
({
width
,
width
,
height
,
height
,
src
,
src
,
sizes
:
processedSizes
,
quality
,
quality
,
alt
:
''
alt
:
''
,
sizes
:
processedSizes
,
loader
:
imageLoader
,
...(
shouldUnoptimize
&&
{
unoptimized
:
true
})
});
});
const
filteredSrcSet
=
processSrcSet
(
props
.
srcSet
,
width
);
const
filteredSrcSet
=
processSrcSet
(
props
.
srcSet
,
width
);
...
...
components/shared/ResponsiveImage/breakpoints.ts
View file @
4266de27
...
@@ -3,7 +3,7 @@ const breakpoints = {
...
@@ -3,7 +3,7 @@ const breakpoints = {
md
:
'(min-width: 768px)'
,
md
:
'(min-width: 768px)'
,
lg
:
'(min-width: 1024px)'
,
lg
:
'(min-width: 1024px)'
,
xl
:
'(min-width: 1280px)'
,
xl
:
'(min-width: 1280px)'
,
xxl
:
'(min-width: 1536px)'
'2xl'
:
'(min-width: 1536px)'
}
as
const
;
}
as
const
;
export
type
BreakpointKey
=
keyof
typeof
breakpoints
;
export
type
BreakpointKey
=
keyof
typeof
breakpoints
;
...
...
components/shared/ResponsiveImage/imageLoader.ts
0 → 100644
View file @
4266de27
import
type
{
ImageLoader
}
from
'next/image'
;
/**
* Image formats that can be optimized/processed
* These formats will be optimized, all other formats will be unoptimized
*/
export
const
processableFormats
=
[
'.jpg'
,
'.jpeg'
,
'.png'
,
'.webp'
,
'.avif'
];
/**
* Checks if an image source should be optimized based on its file extension
* @param src - The image source path
* @returns true if the image format is processable (should be optimized), false otherwise
*/
export
const
shouldOptimizeImage
=
(
src
:
string
):
boolean
=>
{
const
pathname
=
src
.
split
(
'?'
)[
0
];
return
processableFormats
.
some
((
format
)
=>
pathname
.
toLowerCase
().
endsWith
(
format
)
);
};
/**
* Custom image loader for Next.js ResponsiveImage component
* Expects src to be a path starting with /assets/ or /_next/static/media/
* Only processable image formats (jpg, jpeg, png, webp, avif) are transformed
* Other formats (svg, gif, etc.) are returned as-is
*/
export
const
imageLoader
:
ImageLoader
=
({
src
,
width
,
quality
})
=>
{
const
pathname
=
src
.
split
(
'?'
)[
0
];
const
pathMappings
:
Record
<
string
,
string
>
=
{
'/assets/'
:
'/images/'
,
'/_next/static/media/'
:
'/images/static/'
};
const
basePath
=
Object
.
keys
(
pathMappings
).
find
((
path
)
=>
pathname
.
startsWith
(
path
)
);
if
(
!
basePath
)
{
return
src
;
}
if
(
!
shouldOptimizeImage
(
src
))
{
return
src
;
}
const
q
=
quality
??
75
;
return
`
${
pathname
.
replace
(
basePath
,
pathMappings
[
basePath
])}
?w=
${
width
}
&q=
${
q
}
`
;
};
components/shared/ResponsiveImage/index.ts
View file @
4266de27
export
*
from
'./ResponsiveImage'
;
export
*
from
'./ResponsiveImage'
;
export
*
from
'./Source'
;
export
*
from
'./Source'
;
export
*
from
'./imageLoader'
;
components/shared/ResponsiveImage/processSizes.ts
View file @
4266de27
...
@@ -2,7 +2,7 @@ import breakpoints, { type BreakpointKey } from './breakpoints';
...
@@ -2,7 +2,7 @@ import breakpoints, { type BreakpointKey } from './breakpoints';
// Breakpoint order from largest to smallest for proper sizes attribute ordering
// Breakpoint order from largest to smallest for proper sizes attribute ordering
const
breakpointOrderMap
:
Record
<
BreakpointKey
,
number
>
=
{
const
breakpointOrderMap
:
Record
<
BreakpointKey
,
number
>
=
{
xxl
:
0
,
'2xl'
:
0
,
xl
:
1
,
xl
:
1
,
lg
:
2
,
lg
:
2
,
md
:
3
,
md
:
3
,
...
@@ -14,7 +14,7 @@ const breakpointOrderMap: Record<BreakpointKey, number> = {
...
@@ -14,7 +14,7 @@ const breakpointOrderMap: Record<BreakpointKey, number> = {
* and ordering them from largest to smallest breakpoint (required for correct evaluation)
* and ordering them from largest to smallest breakpoint (required for correct evaluation)
* Example: "sm 640px, lg 50vw, 85vw" -> "(min-width: 1024px) 50vw, (min-width: 640px) 640px, 85vw"
* Example: "sm 640px, lg 50vw, 85vw" -> "(min-width: 1024px) 50vw, (min-width: 640px) 640px, 85vw"
*/
*/
export
function
processSizes
(
sizes
:
string
):
string
{
export
function
processSizes
(
sizes
?:
string
):
string
|
undefined
{
if
(
!
sizes
)
return
sizes
;
if
(
!
sizes
)
return
sizes
;
const
parts
=
sizes
.
split
(
','
);
const
parts
=
sizes
.
split
(
','
);
...
@@ -27,10 +27,10 @@ export function processSizes(sizes: string): string {
...
@@ -27,10 +27,10 @@ export function processSizes(sizes: string): string {
if
(
!
trimmed
)
continue
;
if
(
!
trimmed
)
continue
;
// Check if the part starts with a breakpoint name
// Check if the part starts with a breakpoint name
const
breakpointMatch
=
trimmed
.
match
(
/^
(
sm|md|lg|xl|
x
xl
)\s
+/
);
const
breakpointMatch
=
trimmed
.
match
(
/^
(
sm|md|lg|xl|
2
xl
)\s
+/
);
if
(
breakpointMatch
)
{
if
(
breakpointMatch
)
{
const
breakpointKey
=
breakpointMatch
[
1
]
as
BreakpointKey
;
const
breakpointKey
=
breakpointMatch
[
1
]
as
BreakpointKey
;
// Runtime validation: ensure breakpoint exists
// Runtime validation: ensure breakpoint exists
if
(
breakpointKey
in
breakpoints
)
{
if
(
breakpointKey
in
breakpoints
)
{
const
mediaQuery
=
breakpoints
[
breakpointKey
];
const
mediaQuery
=
breakpoints
[
breakpointKey
];
...
...
components/shared/ResponsiveImage/processSrcSet.ts
View file @
4266de27
/**
/**
* Processes the srcSet string to
exclude width descriptors greater than the actual image
width
* Processes the srcSet string to
include widths up to maxWidth, plus one size larger if maxWidth doesn't exactly match any
width
* @param srcSet - The srcSet string from Next.js getImageProps (e.g., "/image.jpg?w=640 640w, /image.jpg?w=1024 1024w")
* @param srcSet - The srcSet string from Next.js getImageProps (e.g., "/image.jpg?w=640 640w, /image.jpg?w=1024 1024w")
* @param maxWidth - The actual width of the image
* @param maxWidth - The actual width of the image
* @returns Filtered srcSet string with only widths <= maxWidth
* @returns Filtered srcSet string with widths <= maxWidth, plus one size up only if maxWidth doesn't exactly match any width
* @example
* // maxWidth exactly matches a width - no next size up included
* processSrcSet("/img.jpg?w=640 640w, /img.jpg?w=1024 1024w, /img.jpg?w=1200 1200w", 1024)
* // Returns: "/img.jpg?w=640 640w, /img.jpg?w=1024 1024w"
*
* @example
* // maxWidth doesn't match any width - includes next size up
* processSrcSet("/img.jpg?w=640 640w, /img.jpg?w=1024 1024w, /img.jpg?w=1200 1200w", 1000)
* // Returns: "/img.jpg?w=640 640w, /img.jpg?w=1024 1024w"
*
* @example
* // maxWidth smaller than all widths - includes smallest width
* processSrcSet("/img.jpg?w=640 640w, /img.jpg?w=1024 1024w", 500)
* // Returns: "/img.jpg?w=640 640w"
*
* @example
* // maxWidth larger than all widths - includes all widths
* processSrcSet("/img.jpg?w=640 640w, /img.jpg?w=1024 1024w", 2000)
* // Returns: "/img.jpg?w=640 640w, /img.jpg?w=1024 1024w"
*
* @example
* // Density descriptors are always included
* processSrcSet("/img.jpg?w=640 640w, /img.jpg?w=1024 1024w, /img.jpg?w=1280 2x", 1000)
* // Returns: "/img.jpg?w=640 640w, /img.jpg?w=1024 1024w, /img.jpg?w=1280 2x"
*/
*/
export
function
processSrcSet
(
srcSet
:
string
|
undefined
,
maxWidth
:
number
):
string
|
undefined
{
export
function
processSrcSet
(
srcSet
:
string
|
undefined
,
maxWidth
:
number
):
string
|
undefined
{
if
(
!
srcSet
||
typeof
srcSet
!==
'string'
)
{
if
(
!
srcSet
||
typeof
srcSet
!==
'string'
)
{
return
srcSet
;
return
srcSet
;
}
}
// Split srcSet by comma to get individual entries
// Split srcSet by comma to get individual entries
const
entries
=
srcSet
.
split
(
','
).
map
(
entry
=>
entry
.
trim
()).
filter
(
Boolean
);
const
entries
=
srcSet
.
split
(
','
)
.
map
((
entry
)
=>
entry
.
trim
())
.
filter
(
Boolean
);
// Filter and process each entry
// First pass: collect all entries with their widths and find the next size up
const
filteredEntries
=
entries
type
EntryWithWidth
=
{
.
map
(
entry
=>
{
entry
:
string
;
// Extract width descriptor (e.g., "640w" from "/image.jpg?w=640 640w")
width
:
number
|
null
;
// Also handle density descriptors (e.g., "2x") - these should be included as-is
};
const
widthMatch
=
entry
.
match
(
/
\s
+
(\d
+
)
w$/
);
const
densityMatch
=
entry
.
match
(
/
\s
+
(\d
+
(?:\.\d
+
)?)
x$/
);
// If it's a density descriptor (e.g., "2x"), include it as-is
const
entriesWithWidths
:
EntryWithWidth
[]
=
entries
.
map
((
entry
)
=>
{
if
(
densityMatch
)
{
// Extract width descriptor (e.g., "640w" from "/image.jpg?w=640 640w")
return
entry
;
const
widthMatch
=
entry
.
match
(
/
\s
+
(\d
+
)
w$/
);
}
const
width
=
widthMatch
?
parseInt
(
widthMatch
[
1
],
10
)
:
null
;
return
{
entry
,
width
};
});
// If it's a width descriptor, check against maxWidth
// Check if maxWidth exactly matches any width in the srcSet
if
(
widthMatch
)
{
const
hasExactMatch
=
entriesWithWidths
.
some
(
const
width
=
parseInt
(
widthMatch
[
1
],
10
);
(
e
)
=>
e
.
width
!==
null
&&
e
.
width
===
maxWidth
);
// Include entry only if width is less than or equal to maxWidth
// Find the smallest width that is greater than maxWidth (one size up)
if
(
width
<=
maxWidth
)
{
// Only include next size up if maxWidth doesn't exactly match any width
return
entry
;
const
widthsGreaterThanMax
=
entriesWithWidths
}
.
filter
((
e
)
=>
e
.
width
!==
null
&&
e
.
width
>
maxWidth
)
.
map
((
e
)
=>
e
.
width
!
);
const
nextSizeUp
=
!
hasExactMatch
&&
widthsGreaterThanMax
.
length
>
0
?
Math
.
min
(...
widthsGreaterThanMax
)
:
null
;
return
null
;
// Filter entries: include widths <= maxWidth, plus the next size up if it exists and maxWidth doesn't match exactly
const
filteredEntries
=
entriesWithWidths
.
filter
(({
width
})
=>
{
// Include density descriptors (they don't have width, so width is null)
if
(
width
===
null
)
{
return
true
;
}
}
// Include widths <= maxWidth
// If no descriptor found, include the entry as-is (fallback)
if
(
width
<=
maxWidth
)
{
return
entry
;
return
true
;
}
// Include the next size up only if maxWidth doesn't exactly match any width
if
(
nextSizeUp
!==
null
&&
width
===
nextSizeUp
)
{
return
true
;
}
return
false
;
})
})
.
filter
((
entry
):
entry
is
string
=>
entry
!==
null
);
.
map
(({
entry
})
=>
entry
);
// Return the filtered srcSet or undefined if empty
// Return the filtered srcSet or undefined if empty
return
filteredEntries
.
length
>
0
?
filteredEntries
.
join
(
', '
)
:
undefined
;
return
filteredEntries
.
length
>
0
?
filteredEntries
.
join
(
', '
)
:
undefined
;
}
}
components/shared/ResponsiveVideo/ResponsiveVideo.tsx
View file @
4266de27
...
@@ -3,24 +3,25 @@
...
@@ -3,24 +3,25 @@
import
React
,
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
'react'
;
import
React
,
{
useCallback
,
useEffect
,
useRef
,
useState
}
from
'react'
;
import
breakpoints
,
{
type
BreakpointKey
}
from
'./breakpoints'
;
import
breakpoints
,
{
type
BreakpointKey
}
from
'./breakpoints'
;
export
type
IResponsiveVideoProps
=
React
.
VideoHTMLAttributes
<
HTMLVideoElement
>
&
{
export
type
IResponsiveVideoProps
=
src
?:
string
;
React
.
VideoHTMLAttributes
<
HTMLVideoElement
>
&
{
/**
src
?:
string
;
* First match in the array will be selected.
/**
* media should be a valid media query or a key from breakpoints object.
* First match in the array will be selected.
*/
* media should be a valid media query or a key from breakpoints object.
srcSet
?:
{
*/
src
:
string
;
srcSet
?:
{
media
:
string
;
src
:
string
;
}[];
media
:
string
;
/** auto plays video source */
}[];
autoPlay
?:
boolean
;
/** auto plays video source */
/**
autoPlay
?:
boolean
;
* If autoplay fails or src is not available and if there is a fallback,
/**
* display this fallback element
* If autoplay fails or src is not available and if there is a fallback,
*/
* display this fallback element
fallback
?:
React
.
ReactNode
;
*/
};
fallback
?:
React
.
ReactNode
;
};
/**
/**
* Resolves media query string from breakpoint key or returns the media query as-is.
* Resolves media query string from breakpoint key or returns the media query as-is.
...
@@ -96,7 +97,8 @@ export const ResponsiveVideo = React.forwardRef<
...
@@ -96,7 +97,8 @@ export const ResponsiveVideo = React.forwardRef<
const
query
=
getMediaQuery
(
media
);
const
query
=
getMediaQuery
(
media
);
return
window
.
matchMedia
(
query
).
matches
;
return
window
.
matchMedia
(
query
).
matches
;
});
});
setCurrentSrc
(
matchedSet
?.
src
||
src
);
const
source
=
matchedSet
?.
src
||
src
;
setCurrentSrc
(
source
);
};
};
selectSource
();
selectSource
();
...
@@ -137,7 +139,9 @@ export const ResponsiveVideo = React.forwardRef<
...
@@ -137,7 +139,9 @@ export const ResponsiveVideo = React.forwardRef<
});
});
},
[
autoPlay
,
currentSrc
]);
},
[
autoPlay
,
currentSrc
]);
const
handleError
=
(
event
:
React
.
SyntheticEvent
<
HTMLVideoElement
,
Event
>
)
=>
{
const
handleError
=
(
event
:
React
.
SyntheticEvent
<
HTMLVideoElement
,
Event
>
)
=>
{
setHasError
(
true
);
setHasError
(
true
);
// Forward the error event to parent component's onError handler if provided
// Forward the error event to parent component's onError handler if provided
onError
?.(
event
);
onError
?.(
event
);
...
@@ -145,7 +149,8 @@ export const ResponsiveVideo = React.forwardRef<
...
@@ -145,7 +149,8 @@ export const ResponsiveVideo = React.forwardRef<
// Show fallback if: autoplay failed, video load error occurred, or no source available.
// Show fallback if: autoplay failed, video load error occurred, or no source available.
// Fallback is only shown if it's provided as a prop.
// Fallback is only shown if it's provided as a prop.
const
shouldShowFallback
=
(
isAutoPlayFailed
||
hasError
||
!
currentSrc
)
&&
fallback
;
const
shouldShowFallback
=
(
isAutoPlayFailed
||
hasError
||
!
currentSrc
)
&&
fallback
;
if
(
shouldShowFallback
)
{
if
(
shouldShowFallback
)
{
return
fallback
;
return
fallback
;
...
@@ -156,12 +161,7 @@ export const ResponsiveVideo = React.forwardRef<
...
@@ -156,12 +161,7 @@ export const ResponsiveVideo = React.forwardRef<
}
}
return
(
return
(
<
video
<
video
ref=
{
setRefs
}
src=
{
currentSrc
}
onError=
{
handleError
}
{
...
rest
}
/>
ref=
{
setRefs
}
src=
{
currentSrc
}
onError=
{
handleError
}
{
...
rest
}
/>
);
);
});
});
...
...
components/shared/ResponsiveVideo/breakpoints.ts
View file @
4266de27
...
@@ -3,7 +3,7 @@ const breakpoints = {
...
@@ -3,7 +3,7 @@ const breakpoints = {
md
:
'(min-width: 768px)'
,
md
:
'(min-width: 768px)'
,
lg
:
'(min-width: 1024px)'
,
lg
:
'(min-width: 1024px)'
,
xl
:
'(min-width: 1280px)'
,
xl
:
'(min-width: 1280px)'
,
xxl
:
'(min-width: 1536px)'
'2xl'
:
'(min-width: 1536px)'
}
as
const
;
}
as
const
;
export
type
BreakpointKey
=
keyof
typeof
breakpoints
;
export
type
BreakpointKey
=
keyof
typeof
breakpoints
;
...
...
next.config.mjs
View file @
4266de27
...
@@ -7,13 +7,16 @@ const getBundleAnalyzer = async () => {
...
@@ -7,13 +7,16 @@ const getBundleAnalyzer = async () => {
return (config) => config;
return (config) => config;
};
};
/** Assets URL is required at build time */
const assetsUrl = new URL(process.env.APP_ASSETS_URL);
/** @type {import('next').NextConfig} */
/** @type {import('next').NextConfig} */
const nextConfig = {
const nextConfig = {
images: {
images: {
remotePatterns: [
remotePatterns: [
{
{
protocol:
'https'
,
protocol:
assetsUrl.protocol.replace(':', '')
,
hostname:
's3-ap-southeast-1.amazonaws.com'
hostname:
assetsUrl.hostname
}
}
],
],
imageSizes: [64, 128, 256, 576, 768, 992, 1200, 1600, 1920, 2048, 3840]
imageSizes: [64, 128, 256, 576, 768, 992, 1200, 1600, 1920, 2048, 3840]
...
@@ -21,8 +24,16 @@ const nextConfig = {
...
@@ -21,8 +24,16 @@ const nextConfig = {
async rewrites() {
async rewrites() {
return [
return [
{
{
source: '/api/:path*',
source: '/images/static/:path*',
destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`
destination: `/_next/image?url=${encodeURIComponent('/_next/static/media' + '/:path*')}`
},
{
source: '/images/:path*',
destination: `/_next/image?url=${encodeURIComponent(assetsUrl.toString() + '/:path*')}`
},
{
source: '/assets/:path*',
destination: `${assetsUrl.toString() + '/:path*'}`
}
}
];
];
},
},
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment