diff --git a/public/keepsimple_/assets/tools/tools-og.png b/public/keepsimple_/assets/tools/tools-og.png new file mode 100644 index 0000000..134c7b7 Binary files /dev/null and b/public/keepsimple_/assets/tools/tools-og.png differ diff --git a/src/components/SeoGenerator/SeoGenerator.tsx b/src/components/SeoGenerator/SeoGenerator.tsx index ca64104..e882d0e 100644 --- a/src/components/SeoGenerator/SeoGenerator.tsx +++ b/src/components/SeoGenerator/SeoGenerator.tsx @@ -18,6 +18,7 @@ interface SeoGeneratorProps { type?: string; forceNoIndex?: boolean; canonicalOverride?: string; + preloadImages?: string[]; ogTags?: { ogDescription: string; ogTitle: string; @@ -47,6 +48,7 @@ const SeoGenerator: FC = ({ type, forceNoIndex, canonicalOverride, + preloadImages, }) => { const router = useRouter(); @@ -190,6 +192,9 @@ const SeoGenerator: FC = ({ type="font/woff2" crossOrigin="anonymous" /> + {preloadImages?.map(src => ( + + ))} {pathname.includes('/user') ? ( diff --git a/src/hooks/usePreloadImages.ts b/src/hooks/usePreloadImages.ts new file mode 100644 index 0000000..47ad47e --- /dev/null +++ b/src/hooks/usePreloadImages.ts @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; + +const usePreloadImages = (urls: string[]) => { + useEffect(() => { + if (!urls?.length) return; + + urls.forEach(src => { + if (!src) return; + const img = new Image(); + img.src = src; + }); + }, [urls]); +}; + +export default usePreloadImages; diff --git a/src/layouts/ToolsLayout/ToolsLayout.tsx b/src/layouts/ToolsLayout/ToolsLayout.tsx index 7a756d8..976ac7e 100644 --- a/src/layouts/ToolsLayout/ToolsLayout.tsx +++ b/src/layouts/ToolsLayout/ToolsLayout.tsx @@ -38,6 +38,22 @@ const ToolsLayout: FC = ({ easterThemeIndexRef.current = easterThemeIndex; }, [easterThemeIndex]); + useEffect(() => { + const easterImages = [ + '/keepsimple_/assets/tools/hero/green.png', + '/keepsimple_/assets/tools/logo/green.svg', + '/keepsimple_/assets/tools/hero/white.png', + '/keepsimple_/assets/tools/logo/white.svg', + '/keepsimple_/assets/tools/hero/black.png', + '/keepsimple_/assets/tools/logo/black.svg', + ]; + + easterImages.forEach(src => { + const img = new Image(); + img.src = src; + }); + }, []); + useEffect(() => { const handleSequence = (event: KeyboardEvent) => { const target = event.target as HTMLElement | null; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index e6b8de6..4d43360 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -168,6 +168,39 @@ function AppContent({ Component, pageProps: { session, ...pageProps } }: TApp) { } }, [isDarkTheme, router]); + useEffect(() => { + const isLongevityProtocolPage = router.asPath.startsWith( + '/tools/longevity-protocol', + ); + if (!isLongevityProtocolPage) return; + + const imagesToPreload = [ + '/keepsimple_/assets/longevity/diet/hearts/sugar.svg', + '/keepsimple_/assets/longevity/diet/hearts/seed-oil.svg', + '/keepsimple_/assets/longevity/diet/hearts/sugary-drinks.svg', + '/keepsimple_/assets/longevity/diet/hearts/ultra-porcessed-food.svg', + '/keepsimple_/assets/longevity/diet/hearts/white-flour.svg', + '/keepsimple_/assets/longevity/diet/hearts/deceptive-food.svg', + '/keepsimple_/assets/longevity/diet/tooltip-line.png', + '/keepsimple_/assets/longevity/diet/damage-icon.svg', + '/keepsimple_/assets/longevity/diet/info-icon.svg', + '/keepsimple_/assets/longevity/diet/examples-icon.svg', + '/keepsimple_/assets/longevity/diet/diet-results-icons/borderline-ok-foods.png', + '/keepsimple_/assets/longevity/diet/diet-results-icons/supportive-foods.png', + '/keepsimple_/assets/longevity/diet/diet-results-icons/protective-foods.png', + '/keepsimple_/assets/longevity/diet/diet-results-icons/clean-nutrients.png', + '/keepsimple_/assets/longevity/diet/diet-results-icons/metabolic-gold.png', + '/keepsimple_/assets/longevity/habits/tooltip-bg.png', + '/keepsimple_/assets/longevity/habits/tooltip-headline-bg.png', + '/keepsimple_/assets/longevity/habits/what-is-this-bg.webp', + ]; + + imagesToPreload.forEach(src => { + const img = new Image(); + img.src = src; + }); + }, [router.asPath]); + useEffect(() => { let handleRouteChange: (url: string) => void; diff --git a/src/pages/tools/index.tsx b/src/pages/tools/index.tsx index 2834664..b95218c 100644 --- a/src/pages/tools/index.tsx +++ b/src/pages/tools/index.tsx @@ -56,6 +56,14 @@ const ToolsPage: FC = ({ tools }) => { ogDescription: tools?.ogDescription, ogTitle: tools?.ogTitle, ogType: tools?.ogType, + ogImage: { + data: { + attributes: { + staticUrl: `${process.env.NEXT_PUBLIC_DOMAIN}/keepsimple_/assets/tools/tools-og.png`, + url: '', + }, + }, + }, }} createdDate={tools?.publishedAt} modifiedDate={tools?.updatedAt} diff --git a/src/pages/tools/longevity-protocol/about-project.tsx b/src/pages/tools/longevity-protocol/about-project.tsx index 22620bc..63fc710 100644 --- a/src/pages/tools/longevity-protocol/about-project.tsx +++ b/src/pages/tools/longevity-protocol/about-project.tsx @@ -1,3 +1,4 @@ +import { getAboutProjectImageUrls } from '@utils/getLongevityImageUrls'; import { GetServerSideProps } from 'next'; import { useRouter } from 'next/router'; @@ -5,6 +6,8 @@ import { ogImage } from '@constants/longevity'; import type { TRouter } from '@local-types/global'; +import usePreloadImages from '@hooks/usePreloadImages'; + import { getAboutProject } from '@api/longevity/about-project'; import SeoGenerator from '@components/SeoGenerator'; @@ -15,6 +18,8 @@ const AboutProject = ({ aboutTheProject }) => { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const imageUrls = getAboutProjectImageUrls(); + usePreloadImages(imageUrls); const OGTags = { ogDescription: aboutTheProject[currentLocale]?.ogDescription || '', ogTitle: aboutTheProject[currentLocale]?.ogTitle || '', @@ -44,6 +49,7 @@ const AboutProject = ({ aboutTheProject }) => { ogTags={OGTags} createdDate={aboutTheProject[currentLocale]?.createdAt || ''} modifiedDate={aboutTheProject[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = environment?.[currentLocale]; + const imageUrls = getEnvironmentImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: environment[currentLocale]?.ogDescription || '', ogTitle: environment[currentLocale]?.ogTitle || '', @@ -41,6 +47,7 @@ const Environment = ({ environment }) => { ogTags={OGTags} createdDate={environment[currentLocale]?.createdAt || ''} modifiedDate={environment[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = dietData?.[currentLocale]; + const imageUrls = getDietImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: dietData?.[currentLocale]?.ogDescription || '', @@ -44,6 +50,7 @@ const Diet = ({ dietData }) => { type={'MedicalWebPage'} createdDate={dietData[currentLocale]?.createdAt || ''} modifiedDate={dietData[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = habitsData?.[currentLocale]; + const imageUrls = getLifestyleImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: habitsData[currentLocale]?.ogDescription || '', @@ -44,6 +50,7 @@ const Lifestyle = ({ habitsData }) => { ogTags={OGTags} createdDate={habitsData[currentLocale]?.createdAt || ''} modifiedDate={habitsData[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const imageUrls = getSleepImageUrls(); + usePreloadImages(imageUrls); const OGTags = { ogDescription: sleepData[currentLocale]?.ogDescription || '', ogTitle: sleepData[currentLocale]?.ogTitle || '', @@ -44,6 +49,7 @@ const Sleep = ({ sleepData, sleepSupplements }) => { ogTags={OGTags} createdDate={sleepData[currentLocale]?.createdAt || ''} modifiedDate={sleepData[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = studyData?.[currentLocale]; + const imageUrls = getStudyImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: studyData[currentLocale]?.ogDescription || '', @@ -44,6 +50,7 @@ const Study = ({ studyData }) => { ogTags={OGTags} createdDate={studyData[currentLocale]?.createdAt || ''} modifiedDate={studyData[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = supplements?.[currentLocale]; + const imageUrls = getSupplementsImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: supplements[currentLocale]?.ogDescription || '', ogTitle: supplements[currentLocale]?.ogTitle || '', @@ -43,6 +49,7 @@ const Supplements = ({ supplements }) => { ogTags={OGTags} createdDate={supplements[currentLocale]?.createdAt || ''} modifiedDate={supplements[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = workoutData?.[currentLocale]; + const imageUrls = getWorkoutImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: workoutData[currentLocale]?.ogDescription || '', ogTitle: workoutData[currentLocale]?.ogTitle || '', @@ -43,6 +49,7 @@ const Workout = ({ workoutData }) => { ogTags={OGTags} createdDate={workoutData[currentLocale]?.createdAt || ''} modifiedDate={workoutData[currentLocale]?.updatedAt || ''} + preloadImages={imageUrls} /> { const router = useRouter(); const { locale } = router as TRouter; const currentLocale = locale === 'ru' ? 'ru' : 'en'; + const data = yearlyResults?.[currentLocale]; + const imageUrls = getResultsImageUrls(data); + usePreloadImages(imageUrls); const OGTags = { ogDescription: yearlyResults[currentLocale]?.ogDescription || '', ogTitle: yearlyResults[currentLocale]?.ogTitle || '', @@ -43,6 +49,7 @@ const Results = ({ yearlyResults }) => { ogTags={OGTags} createdDate={yearlyResults[currentLocale]?.createdAt} modifiedDate={yearlyResults[currentLocale]?.updatedAt} + preloadImages={imageUrls} /> + url ? `${strapiUrl}${url}` : null; + +const extractUrl = (field: any): string | null => + toFullUrl(field?.data?.attributes?.url); + +export const getDietImageUrls = (data: any) => { + return [extractUrl(data?.['background image'])].filter(Boolean) as string[]; +}; + +export const getWorkoutImageUrls = (data: any) => { + return [extractUrl(data?.['image'])].filter(Boolean) as string[]; +}; + +export const getEnvironmentImageUrls = (data: any) => { + const backgroundUrl = extractUrl(data?.['image']); + + const iconUrls = [ + ...(data?.['home'] || []), + ...(data?.['principles'] || []), + ...(data?.['data tracking'] || []), + ] + .map((item: any) => extractUrl(item?.icon)) + .filter(Boolean); + + return [backgroundUrl, ...iconUrls].filter(Boolean) as string[]; +}; + +export const getLifestyleImageUrls = (data: any) => { + return [extractUrl(data?.['background image'])].filter(Boolean) as string[]; +}; + +export const getResultsImageUrls = (data: any) => { + return [extractUrl(data?.['background image'])].filter(Boolean) as string[]; +}; + +export const getStudyImageUrls = (data: any) => { + const backgroundUrl = extractUrl(data?.['background image']); + const chartUrls = [ + 'books flipped card image', + 'books notes flipped card image', + 'daily work flipped card image', + 'research tasks flipped card image', + 'data flipped card image', + 'hacks flipped card image', + ] + .map(key => extractUrl(data?.[key])) + .filter(Boolean); + + return [backgroundUrl, ...chartUrls].filter(Boolean) as string[]; +}; + +export const getSupplementsImageUrls = (data: any) => { + return [extractUrl(data?.['image'])].filter(Boolean) as string[]; +}; + +const sleepImgPath = '/keepsimple_/assets/longevity/sleep/'; + +export const getSleepImageUrls = () => { + return [ + `${sleepImgPath}supplements-header.png`, + `${sleepImgPath}key-brain-rules-header.png`, + `${sleepImgPath}used-devices-header.png`, + `${sleepImgPath}sleep-hacks.png`, + '/keepsimple_/assets/longevity/shared-assets/small-table.svg', + '/keepsimple_/assets/longevity/shared-assets/right-arrow.svg', + ]; +}; + +const basicStatsImgPath = '/keepsimple_/assets/longevity/basic-stats/'; + +export const getAboutProjectImageUrls = () => { + return [ + `${basicStatsImgPath}gender.svg`, + `${basicStatsImgPath}age.svg`, + `${basicStatsImgPath}height.svg`, + `${basicStatsImgPath}weight.svg`, + `${basicStatsImgPath}occupation.svg`, + ]; +}; diff --git a/tsconfig.json b/tsconfig.json index 2ca26e8..af4a3d6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,6 +28,7 @@ "@api/*": ["src/api/*"], "@styles/*": ["src/styles/*"], "@local-types/*": ["src/local-types/*"], + "@utils/*": ["src/utils/*"], "@icons/*": ["src/assets/icons/*"], "react": ["node_modules/@types/react"] },