Refactor link prefetching
This commit is contained in:
parent
52de4718cb
commit
f49e0678c9
12
package.json
12
package.json
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.556.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.556.0",
|
||||
"@next/bundle-analyzer": "14.2.1",
|
||||
"@next/bundle-analyzer": "14.2.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
@ -25,9 +25,9 @@
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"@upstash/ratelimit": "^1.1.1",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/blob": "^0.23.0",
|
||||
"@vercel/blob": "^0.23.2",
|
||||
"@vercel/kv": "^1.0.1",
|
||||
"@vercel/postgres": "0.8.0",
|
||||
"@vercel/postgres": "^0.8.0",
|
||||
"@vercel/speed-insights": "^1.0.10",
|
||||
"ai": "^3.0.23",
|
||||
"autoprefixer": "10.4.19",
|
||||
@ -36,16 +36,16 @@
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-next": "14.2.1",
|
||||
"eslint-config-next": "14.2.2",
|
||||
"exifr": "^7.1.3",
|
||||
"framer-motion": "^11.1.3",
|
||||
"framer-motion": "^11.1.5",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"next": "14.3.0-canary.8",
|
||||
"next-auth": "5.0.0-beta.15",
|
||||
"next-themes": "^0.3.0",
|
||||
"openai": "^4.37.1",
|
||||
"openai": "^4.38.0",
|
||||
"postcss": "8.4.38",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@ -15,8 +15,8 @@ importers:
|
||||
specifier: 3.556.0
|
||||
version: 3.556.0
|
||||
'@next/bundle-analyzer':
|
||||
specifier: 14.2.1
|
||||
version: 14.2.1(bufferutil@4.0.8)(utf-8-validate@6.0.3)
|
||||
specifier: 14.2.2
|
||||
version: 14.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@ -57,13 +57,13 @@ importers:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(next@14.3.0-canary.8(@babel/core@7.23.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
|
||||
'@vercel/blob':
|
||||
specifier: ^0.23.0
|
||||
version: 0.23.0
|
||||
specifier: ^0.23.2
|
||||
version: 0.23.2
|
||||
'@vercel/kv':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
'@vercel/postgres':
|
||||
specifier: 0.8.0
|
||||
specifier: ^0.8.0
|
||||
version: 0.8.0
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.0.10
|
||||
@ -90,14 +90,14 @@ importers:
|
||||
specifier: 8.57.0
|
||||
version: 8.57.0
|
||||
eslint-config-next:
|
||||
specifier: 14.2.1
|
||||
version: 14.2.1(eslint@8.57.0)(typescript@5.4.5)
|
||||
specifier: 14.2.2
|
||||
version: 14.2.2(eslint@8.57.0)(typescript@5.4.5)
|
||||
exifr:
|
||||
specifier: ^7.1.3
|
||||
version: 7.1.3
|
||||
framer-motion:
|
||||
specifier: ^11.1.3
|
||||
version: 11.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
specifier: ^11.1.5
|
||||
version: 11.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.12.7)
|
||||
@ -117,8 +117,8 @@ importers:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
openai:
|
||||
specifier: ^4.37.1
|
||||
version: 4.37.1
|
||||
specifier: ^4.38.0
|
||||
version: 4.38.0
|
||||
postcss:
|
||||
specifier: 8.4.38
|
||||
version: 8.4.38
|
||||
@ -688,14 +688,14 @@ packages:
|
||||
'@neondatabase/serverless@0.7.2':
|
||||
resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==}
|
||||
|
||||
'@next/bundle-analyzer@14.2.1':
|
||||
resolution: {integrity: sha512-Qwy3Mu/dfnu4rs2xzCy7gKZlwzZzYtiq/rjPcK/7xq3BHSyLthkHf1NAF8NNfjVTouDwo2KchisHrmAamUNWWw==}
|
||||
'@next/bundle-analyzer@14.2.2':
|
||||
resolution: {integrity: sha512-Zp2xG3VTPHUquOcBaRtrr0/n7mqnjKUmprGcJXPEKGgP5rAsLymIfWKm3jIVWIw5Eb4fNOfX4v+L+qiSvs+OJw==}
|
||||
|
||||
'@next/env@14.3.0-canary.8':
|
||||
resolution: {integrity: sha512-vkUEnZHkrSnJjC9t9U1aQf/SQ0wR0t1jjbLBgYWgJPesQLGnhogmMm2ScROdKTB6iVjz6IiLtjOdgWTKGIgkSg==}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.1':
|
||||
resolution: {integrity: sha512-Fp+mthEBjkn8r9qd6o4JgxKp0IDEzW0VYHD8ZC05xS5/lFNwHKuOdr2kVhWG7BQCO9L6eeepshM1Wbs2T+LgSg==}
|
||||
'@next/eslint-plugin-next@14.2.2':
|
||||
resolution: {integrity: sha512-q+Ec2648JtBpKiu/FSJm8HAsFXlNvioHeBCbTP12T1SGcHYwhqHULSfQgFkPgHDu3kzNp2Kem4J54bK4rPQ5SQ==}
|
||||
|
||||
'@next/swc-darwin-arm64@14.3.0-canary.8':
|
||||
resolution: {integrity: sha512-dnWrvcTS0sYJpOTU/p98x7M8lpRKiIF+gQ164yyKNVC57vDh0zbSIKweQjvlLCeFVGli0QjboDrUV/j3UMbqxg==}
|
||||
@ -1498,8 +1498,8 @@ packages:
|
||||
react:
|
||||
optional: true
|
||||
|
||||
'@vercel/blob@0.23.0':
|
||||
resolution: {integrity: sha512-FLXiy4SCXJ39gov5qnw7I5YPQq3NBUh5z+swcPw6lBdWN4YQoOho6cVMVncsu1Hc8kemth6ZaTZAnWrNRtbaZw==}
|
||||
'@vercel/blob@0.23.2':
|
||||
resolution: {integrity: sha512-wejIdxb/CJkQpV18/TgGDuQFzZ2BCV+T2F786bmtt7LytmJmfhH8wHuJ70odOyvmiO3RIL57ht5GLZmeV8jqiA==}
|
||||
engines: {node: '>=16.14'}
|
||||
|
||||
'@vercel/kv@1.0.1':
|
||||
@ -2150,8 +2150,8 @@ packages:
|
||||
engines: {node: '>=6.0'}
|
||||
hasBin: true
|
||||
|
||||
eslint-config-next@14.2.1:
|
||||
resolution: {integrity: sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==}
|
||||
eslint-config-next@14.2.2:
|
||||
resolution: {integrity: sha512-12/uFc0KX+wUs7EDpOUGKMXBXZJiBVGdK5/m/QgXOCg2mQ0bQWoKSWNrCeOg7Vum6Kw1d1TW453W6xh+GbHquw==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0
|
||||
typescript: '>=3.3.1'
|
||||
@ -2352,8 +2352,8 @@ packages:
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
framer-motion@11.1.3:
|
||||
resolution: {integrity: sha512-/t74b1WQu+mpZtra6xFSfsRfdTymJjNYgFudVIsUmoOWjznr3x5o9HbrX7Jt9655OCA2Js0W79bMZEKE7owp9w==}
|
||||
framer-motion@11.1.5:
|
||||
resolution: {integrity: sha512-ogK5fc0GBUT3AjzMXPx7f74m5V1ByRqkKQARBVHpdkYTNDxb/WriANYD+5JBo1wklQQJ1HayDZtmofQLqZFcbw==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0
|
||||
@ -3206,8 +3206,8 @@ packages:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
openai@4.37.1:
|
||||
resolution: {integrity: sha512-YVuhylpDeTNCWgsfhZe38+c4dDWZuW9VgzNY/sdYiNt6K9pvijroyYENp8YGEUHnuIAKtsLneZX9Qb/iB5XHkw==}
|
||||
openai@4.38.0:
|
||||
resolution: {integrity: sha512-q1w04cRm+7CgUAGDXqt+OMa89zXBffHrEK0FcVDRhD+zL1S1aAatu4iYO5sIxR2QFEP//i8CM3QaxGVTNajxuw==}
|
||||
hasBin: true
|
||||
|
||||
opener@1.5.2:
|
||||
@ -3279,8 +3279,8 @@ packages:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
pg-protocol@1.6.0:
|
||||
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
|
||||
pg-protocol@1.6.1:
|
||||
resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==}
|
||||
|
||||
pg-types@2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||
@ -5148,7 +5148,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/pg': 8.6.6
|
||||
|
||||
'@next/bundle-analyzer@14.2.1(bufferutil@4.0.8)(utf-8-validate@6.0.3)':
|
||||
'@next/bundle-analyzer@14.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)':
|
||||
dependencies:
|
||||
webpack-bundle-analyzer: 4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.3)
|
||||
transitivePeerDependencies:
|
||||
@ -5157,7 +5157,7 @@ snapshots:
|
||||
|
||||
'@next/env@14.3.0-canary.8': {}
|
||||
|
||||
'@next/eslint-plugin-next@14.2.1':
|
||||
'@next/eslint-plugin-next@14.2.2':
|
||||
dependencies:
|
||||
glob: 10.3.10
|
||||
|
||||
@ -5940,7 +5940,7 @@ snapshots:
|
||||
'@types/pg@8.6.6':
|
||||
dependencies:
|
||||
'@types/node': 20.12.7
|
||||
pg-protocol: 1.6.0
|
||||
pg-protocol: 1.6.1
|
||||
pg-types: 2.2.0
|
||||
|
||||
'@types/prop-types@15.7.11': {}
|
||||
@ -6117,7 +6117,7 @@ snapshots:
|
||||
next: 14.3.0-canary.8(@babel/core@7.23.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
|
||||
'@vercel/blob@0.23.0':
|
||||
'@vercel/blob@0.23.2':
|
||||
dependencies:
|
||||
async-retry: 1.3.3
|
||||
bytes: 3.1.2
|
||||
@ -6845,9 +6845,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
source-map: 0.6.1
|
||||
|
||||
eslint-config-next@14.2.1(eslint@8.57.0)(typescript@5.4.5):
|
||||
eslint-config-next@14.2.2(eslint@8.57.0)(typescript@5.4.5):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 14.2.1
|
||||
'@next/eslint-plugin-next': 14.2.2
|
||||
'@rushstack/eslint-patch': 1.7.2
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
|
||||
eslint: 8.57.0
|
||||
@ -7160,7 +7160,7 @@ snapshots:
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@11.1.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
framer-motion@11.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
optionalDependencies:
|
||||
@ -8166,7 +8166,7 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
openai@4.37.1:
|
||||
openai@4.38.0:
|
||||
dependencies:
|
||||
'@types/node': 18.19.24
|
||||
'@types/node-fetch': 2.6.11
|
||||
@ -8246,7 +8246,7 @@ snapshots:
|
||||
|
||||
pg-int8@1.0.1: {}
|
||||
|
||||
pg-protocol@1.6.0: {}
|
||||
pg-protocol@1.6.1: {}
|
||||
|
||||
pg-types@2.2.0:
|
||||
dependencies:
|
||||
|
||||
@ -3,7 +3,6 @@ import {
|
||||
altTextForPhoto,
|
||||
shouldShowCameraDataForPhoto,
|
||||
shouldShowExifDataForPhoto,
|
||||
titleForPhoto,
|
||||
} from '.';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import ImageLarge from '@/components/ImageLarge';
|
||||
@ -19,6 +18,7 @@ import { sortTags } from '@/tag';
|
||||
import AdminPhotoMenu from '@/admin/AdminPhotoMenu';
|
||||
import { Suspense } from 'react';
|
||||
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
|
||||
import PhotoLink from './PhotoLink';
|
||||
|
||||
export default function PhotoLarge({
|
||||
photo,
|
||||
@ -81,14 +81,11 @@ export default function PhotoLarge({
|
||||
{/* Meta */}
|
||||
<div className="pr-2 md:pr-0">
|
||||
<div className="md:relative flex gap-2 items-start">
|
||||
<div className="flex-grow">
|
||||
<Link
|
||||
href={pathForPhoto(photo)}
|
||||
className="font-bold uppercase"
|
||||
>
|
||||
{titleForPhoto(photo)}
|
||||
</Link>
|
||||
</div>
|
||||
<PhotoLink
|
||||
photo={photo}
|
||||
className="font-bold uppercase flex-grow"
|
||||
prefetch={prefetch}
|
||||
/>
|
||||
<Suspense>
|
||||
<div className="absolute right-0 translate-y-[-4px] z-10">
|
||||
<AdminPhotoMenu photo={photo} />
|
||||
@ -106,9 +103,14 @@ export default function PhotoLarge({
|
||||
<PhotoCamera
|
||||
camera={camera}
|
||||
contrast="medium"
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>}
|
||||
{showTagsContent &&
|
||||
<PhotoTags tags={tags} contrast="medium" />}
|
||||
<PhotoTags
|
||||
tags={tags}
|
||||
contrast="medium"
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
@ -138,6 +140,7 @@ export default function PhotoLarge({
|
||||
{showSimulation && photo.filmSimulation &&
|
||||
<PhotoFilmSimulation
|
||||
simulation={photo.filmSimulation}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>}
|
||||
</>}
|
||||
<div className={clsx(
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { Photo } from '@/photo';
|
||||
import { Photo, titleForPhoto } from '@/photo';
|
||||
import Link from 'next/link';
|
||||
import { AnimationConfig } from '../components/AnimateItems';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import { pathForPhoto } from '@/site/paths';
|
||||
import { Camera } from '@/camera';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import { clsx } from 'clsx/lite';
|
||||
|
||||
export default function PhotoLink({
|
||||
photo,
|
||||
@ -16,6 +17,7 @@ export default function PhotoLink({
|
||||
simulation,
|
||||
prefetch,
|
||||
nextPhotoAnimation,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
photo?: Photo
|
||||
@ -24,7 +26,8 @@ export default function PhotoLink({
|
||||
simulation?: FilmSimulation
|
||||
prefetch?: boolean
|
||||
nextPhotoAnimation?: AnimationConfig
|
||||
children: ReactNode
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
}) {
|
||||
const { setNextPhotoAnimation } = useAppState();
|
||||
|
||||
@ -38,12 +41,16 @@ export default function PhotoLink({
|
||||
setNextPhotoAnimation?.(nextPhotoAnimation);
|
||||
}
|
||||
}}
|
||||
className={className}
|
||||
scroll={false}
|
||||
>
|
||||
{children}
|
||||
{children ?? titleForPhoto(photo)}
|
||||
</Link>
|
||||
: <span className="text-gray-300 dark:text-gray-700 cursor-default">
|
||||
{children}
|
||||
: <span className={clsx(
|
||||
'text-gray-300 dark:text-gray-700 cursor-default',
|
||||
className,
|
||||
)}>
|
||||
{children ?? (photo ? titleForPhoto(photo) : undefined)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@ -301,9 +301,7 @@ const safelyQueryPhotos = async <T>(
|
||||
): Promise<T> => {
|
||||
let result: T;
|
||||
|
||||
if (debugMessage) {
|
||||
console.log(`Executing sql query: ${debugMessage}`);
|
||||
}
|
||||
const start = new Date();
|
||||
|
||||
try {
|
||||
result = await callback();
|
||||
@ -337,6 +335,12 @@ const safelyQueryPhotos = async <T>(
|
||||
}
|
||||
}
|
||||
|
||||
if (debugMessage) {
|
||||
const time =
|
||||
(((new Date()).getTime() - start.getTime()) / 1000).toFixed(2);
|
||||
console.log(`Executing sql query: ${debugMessage} (${time} seconds)`);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -412,8 +416,7 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => {
|
||||
values.push(limit, offset);
|
||||
|
||||
return safelyQueryPhotos(async () => {
|
||||
const client = await db.connect();
|
||||
return client.query(sql.join(' '), values);
|
||||
return db.query(sql.join(' '), values);
|
||||
}, sql.join(' '))
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
};
|
||||
@ -427,8 +430,7 @@ export const getPhotosNearId = async (
|
||||
: 'ORDER BY taken_at DESC';
|
||||
|
||||
return safelyQueryPhotos(async () => {
|
||||
const client = await db.connect();
|
||||
return client.query(
|
||||
return db.query(
|
||||
`
|
||||
WITH twi AS (
|
||||
SELECT *, row_number()
|
||||
@ -444,7 +446,7 @@ export const getPhotosNearId = async (
|
||||
`,
|
||||
[id, limit]
|
||||
);
|
||||
}, 'getPhotosNearId')
|
||||
}, `getPhotosNearId: ${id}`)
|
||||
.then(({ rows }) => {
|
||||
const photos = rows.map(parsePhotoFromDb);
|
||||
return {
|
||||
|
||||
@ -22,8 +22,10 @@ const SITE_DOMAIN =
|
||||
VERCEL_PROJECT_URL;
|
||||
|
||||
// Used primarily for absolute references such as OG images
|
||||
export const BASE_URL = makeUrlAbsolute(VERCEL_ENV === 'production'
|
||||
? SITE_DOMAIN
|
||||
export const BASE_URL = makeUrlAbsolute((
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
VERCEL_ENV !== 'preview'
|
||||
) ? SITE_DOMAIN
|
||||
: VERCEL_ENV === 'preview'
|
||||
? VERCEL_BRANCH_URL || VERCEL_DEPLOYMENT_URL
|
||||
: 'http://localhost:3000')?.toLocaleLowerCase();
|
||||
|
||||
@ -6,6 +6,7 @@ import { EntityLinkExternalProps } from '@/components/primitives/EntityLink';
|
||||
export default function PhotoTags({
|
||||
tags,
|
||||
contrast,
|
||||
prefetch,
|
||||
}: {
|
||||
tags: string[]
|
||||
} & EntityLinkExternalProps) {
|
||||
@ -14,8 +15,8 @@ export default function PhotoTags({
|
||||
{tags.map(tag =>
|
||||
<>
|
||||
{isTagFavs(tag)
|
||||
? <FavsTag key={tag} {...{ contrast }} />
|
||||
: <PhotoTag key={tag} {...{ tag, contrast }} />}
|
||||
? <FavsTag key={tag} {...{ contrast, prefetch }} />
|
||||
: <PhotoTag key={tag} {...{ tag, contrast, prefetch }} />}
|
||||
</>)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user