Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve: step 1 of image optimization #99

Merged
merged 4 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions config/base.next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const IS_PROD = process.env.NODE_ENV === "production";
const isProd = process.env.NODE_ENV === "production";
const cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME || "";

// The folders containing files importing twin.macro
const path = require("path");
Expand Down Expand Up @@ -62,16 +63,10 @@ module.exports = () => ({
return config;
},
images: {
unoptimized: true,
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
unoptimized: !isProd || !cloudName,
// Link: https://fe-developers.kakaoent.com/2022/220714-next-image/
imageSizes: [64, 256],
deviceSizes: [512],
deviceSizes: [440],
imageSizes: [100, 200],
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
sentry: {
Expand All @@ -81,6 +76,6 @@ module.exports = () => ({
// https://webpack.js.org/configuration/devtool/ and
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
// for more information.
hideSourceMaps: IS_PROD,
hideSourceMaps: isProd,
},
});
26 changes: 26 additions & 0 deletions config/cloudinary-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import type { ImageLoaderProps } from "next/image";

const cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME || "";

export function cloudinaryLoader({ src, width, quality }: ImageLoaderProps) {
const rawTransformations = ["f_auto", "c_limit", `w_${width}`, `q_${quality || "auto"}`];
let isAbsolute: boolean;
let href: string;

if (src.startsWith("/")) {
href = src;
isAbsolute = false;
} else {
const hrefParsed = new URL(src);
href = hrefParsed.toString();
isAbsolute = true;
}

const cldUrl = `https://res.cloudinary.com/${cloudName}/image/fetch/${rawTransformations.join(
",",
)}/${href}`;

return isAbsolute ? cldUrl : src;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudinaryLoader } from "config/cloudinary-loader";
import Link from "next/link";

import { Icon } from "@/common/components/Icon";
Expand All @@ -17,7 +18,13 @@ export const LoginSideBarContent = (props: LoginSideBarContentProps) => {
<>
<div className="w-full rounded-24 bg-gray-100 px-16 pt-16 pb-24">
<Link className="flex items-center gap-12" href="/mypage">
<Photo className="h-50 w-50 rounded-20" src={user?.imageUrl} />
<Photo
alt={`${user?.name || ""}์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€`}
className="h-50 w-50 rounded-20"
loader={cloudinaryLoader}
sizes="50px"
src={user?.imageUrl || ""}
/>
<span className="grow text-left text-18-bold-140 text-gray-900">{user?.name}</span>
<Icon name="setting" />
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ export const LogoutSideBarContent = (props: LogoutSideBarContentProps) => {
<>
<button className="w-full rounded-24 bg-gray-100 px-16 pt-16 pb-24" onClick={validate()}>
<div className="flex items-center gap-12">
<Photo className="h-50 w-50 rounded-20" src={defaultAvatarUrl} />
<Photo
unoptimized
alt="๊ธฐ๋ณธ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€"
className="h-50 w-50 rounded-20"
sizes="50px"
src={defaultAvatarUrl}
/>
<span className="grow text-left text-18-bold-140 text-gray-900">{defaultName}</span>
<Icon name="setting" />
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/common/components/Photo/Photo.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export const Default = () => (
<>
<h1 className="text-20-bold-140">Resizeable Photo</h1>
<h2>width: 100, height: 100</h2>
<Photo className="h-100 w-100" src={IMAGE_SRC} />
<Photo unoptimized alt="์˜ˆ์‹œ ์ด๋ฏธ์ง€" className="h-100 w-100" src={IMAGE_SRC} />
<hr />
<h2>width: 100, height: 200</h2>
<Photo className="h-200 w-100" src={IMAGE_SRC} />
<Photo unoptimized alt="์˜ˆ์‹œ ์ด๋ฏธ์ง€" className="h-200 w-100" src={IMAGE_SRC} />
<hr />
<h2>Render fallback image(Wrong image src)</h2>
<Photo className="h-200 w-100" src="" />
<Photo unoptimized alt="์˜ˆ์‹œ ์ด๋ฏธ์ง€" className="h-200 w-100" src="" />
</>
);
14 changes: 7 additions & 7 deletions src/common/components/Photo/Photo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@ import { useEffect, useState } from "react";
/**
* NOTE
* ComponentProps<typeof Image> ์œผ๋กœ ํƒ€์ž… ์„ ์–ธํ•˜๋ฉด
* storybook jsdoc parse ์˜ค๋ฅ˜ ๋ฐœ์ƒ
* - interface ๋‚ด๋ถ€์— @deprecated ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ์œผ๋ฉด ๋ฌธ์ œ ์ƒ๊ธฐ๋Š” ๋“ฏ ๋ณด์ž„
* storybook jsdoc parse ์˜ค๋ฅ˜ ๋ฐœ์ƒ. interface ๋‚ด๋ถ€์— @deprecated ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ์œผ๋ฉด ๋ฌธ์ œ ์ƒ๊ธฐ๋Š” ๋“ฏ ๋ณด์ž„
* storybook v7์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ์‹œ ๋ฌธ์ œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•จ
*/
interface Props extends Omit<ComponentProps<"img">, "placeholder"> {
interface Props extends ComponentProps<typeof Image> {
fallbackSrc?: string;
priority?: boolean;
unoptimized?: boolean;
}
const base64Blur =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mO8/Z8BAzAOZUEAQ+ESj6kXXm0AAAAASUVORK5CYII=";

const fallback = "/img/fallbackImage.png";

export const Photo = ({
src = "",
alt = "thumbnail",
src,
className = "",
width,
height,
fallbackSrc = fallback,
alt,
...props
}: Props) => {
/**
* FIX
Expand Down Expand Up @@ -55,6 +54,7 @@ export const Photo = ({
src={isFailLoading ? fallbackSrc : src}
style={{ objectFit: "cover" }}
onError={setIsFailLoading}
{...props}
/>
</div>
);
Expand Down
12 changes: 10 additions & 2 deletions src/common/components/RandomImge/RandomImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ComponentProps } from "react";

import { Photo } from "../Photo";

interface Props extends ComponentProps<typeof Photo> {
interface Props extends Omit<ComponentProps<typeof Photo>, "alt" | "src" | "sizes"> {
images?: { name: string; src: string }[];
}
const randomImages = [
Expand Down Expand Up @@ -33,5 +33,13 @@ const randomImages = [
];
export const RandomImage = ({ images = randomImages, className = "" }: Props) => {
const randomImage = images[Math.floor(Math.random() * images.length)];
return <Photo alt={randomImage.name} className={className} sizes="32px" src={randomImage.src} />;
return (
<Photo
unoptimized
alt={randomImage.name}
className={className}
sizes="32px"
src={randomImage.src}
/>
);
};
2 changes: 1 addition & 1 deletion src/features/common/components/InfiniteMemeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MemeItem } from "@/features/common";
import type { Meme } from "@/types";

interface InfiniteMemeListProps {
memeList: Meme[];
memeList: (Meme & { priority?: boolean })[];
onRequestAppend: () => void;
}

Expand Down
6 changes: 4 additions & 2 deletions src/features/common/components/MemeItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudinaryLoader } from "config/cloudinary-loader";
import { memo } from "react";

import { Icon } from "@/common/components/Icon";
Expand All @@ -8,7 +9,7 @@ import { MemeActionSheet, useMoveMemeDetail } from "@/features/common";
import type { Meme } from "@/types";

interface Props {
meme: Meme;
meme: Meme & { priority?: boolean };
onClick?: (id: number) => void;
}

Expand All @@ -32,7 +33,8 @@ export const MemeItem = memo(({ meme, onClick }: Props) => {
className="rounded-16"
draggable={false}
height={image.images[0]?.imageHeight}
sizes="100px"
loader={cloudinaryLoader}
sizes="200px"
src={image.images[0]?.imageUrl}
unoptimized={isEncodingError(image.images[0]?.imageUrl)}
width={image.images[0]?.imageWidth}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ export const CategoryContent = () => {
gtmTrigger[category.name]
} flex w-full items-center justify-between gap-8 rounded-full px-4 py-12 text-16-semibold-140 [&>span>#chevronDown]:data-[state=open]:rotate-180`}
>
<Photo className="h-24 w-24 p-2" loading="eager" src={category.icon} />
<Photo
unoptimized
alt={category.name}
className="h-24 w-24 p-2"
loading="eager"
src={category.icon}
/>
<span className="flex-grow text-left text-16-semibold-140">
{category.mainTags.length ? (
<SlotCategory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ export const FavoriteCategory = () => {
<Item value={FAVORITE_ID}>
<Header className="py-4">
<Trigger className="flex w-full items-center justify-between gap-8 rounded-full px-4 py-12 text-16-semibold-140 [&>span>#chevronDown]:data-[state=open]:rotate-180">
<Photo className="h-24 w-24 p-2" loading="eager" src={FAVORITE_ICON} />
<Photo
unoptimized
alt="๋ถ๋งˆํฌ"
className="h-24 w-24 p-2"
loading="eager"
src={FAVORITE_ICON}
/>
<span className="flex-grow text-left text-16-semibold-140">{FAVORITE_ID}</span>
<span className="flex h-40 w-40 items-center justify-center rounded-full hover:bg-gray-100">
<Icon
Expand Down
19 changes: 16 additions & 3 deletions src/features/explore/tags/components/MemesByTagsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudinaryLoader } from "config/cloudinary-loader";
import { useRouter } from "next/router";
import * as React from "react";

Expand All @@ -18,7 +19,11 @@ export const MemesByTagsContainer = ({ tag }: Props) => {
}
return (
<>
<Thumbnail image={memeList?.[0].image.images[0].imageUrl} totalCount={totalCount as number} />
<Thumbnail
image={memeList?.[0].image.images[0].imageUrl}
tag={tag}
totalCount={totalCount as number}
/>
<InfiniteMemeList
memeList={memeList}
onRequestAppend={() => fetchNextPage({ cancelRefetch: false })}
Expand All @@ -29,9 +34,10 @@ export const MemesByTagsContainer = ({ tag }: Props) => {

interface ThumbnailProps {
image: string;
tag: string;
totalCount: number;
}
const Thumbnail = React.memo(function Thumbnail({ image, totalCount }: ThumbnailProps) {
const Thumbnail = React.memo(function Thumbnail({ image, tag, totalCount }: ThumbnailProps) {
const router = useRouter();
const clipboard = useClipboard();
const toast = useToast();
Expand All @@ -40,7 +46,14 @@ const Thumbnail = React.memo(function Thumbnail({ image, totalCount }: Thumbnail

return (
<div className="flex gap-16 px-22 pt-16 pb-24">
<Photo className="h-80 w-80 rounded-full" src={image} />
<Photo
priority
alt={`${tag} ๋ฐˆ ์ธ๋„ค์ผ`}
className="h-80 w-80 rounded-full"
loader={cloudinaryLoader}
sizes="80px"
src={image}
/>
<div className="flex flex-1 flex-col items-center justify-center gap-2">
<span className="text-14-semibold-140 text-gray-900">{totalCount}๊ฐœ ๋ฐˆ</span>
<button
Expand Down
3 changes: 2 additions & 1 deletion src/features/memes/components/MemeDetail/MemeDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ export const MemeDetail = ({ id }: Props) => {
>
<Photo
priority
unoptimized
alt={name}
className="max-h-[70vh] min-h-[25vh] w-full rounded-15"
height={imageHeight}
sizes="200px"
src={imageUrl}
width={imageWidth}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cloudinaryLoader } from "config/cloudinary-loader";

import { usePostMemeToSharedCollection } from "@/api/collection";
import { useGetMemeDetailById } from "@/api/meme";
import { Modal } from "@/common/components/Modal";
Expand Down Expand Up @@ -39,7 +41,13 @@ export const MemeShareModal = ({ id, isOpen, onClose }: Props) => {
return (
<Modal open={isOpen} onClose={onClose}>
<Modal.Header />
<Photo className="my-24 h-300 w-300 rounded-15" src={src} />
<Photo
alt={name}
className="my-24 h-300 w-300 rounded-15"
loader={cloudinaryLoader}
sizes="300px"
src={src}
/>
<ul className="mx-auto mb-32 flex h-77 w-fit gap-16 whitespace-nowrap text-gray-600">
<li className="relative flex flex-col items-center gap-8">
<KakaoShareButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudinaryLoader } from "config/cloudinary-loader";
import type { HTMLAttributes } from "react";

import { Photo } from "@/common/components/Photo";
Expand All @@ -18,6 +19,7 @@ export const SearchPopularItem = ({ name, imageSrc, ...rest }: Props) => {
alt={name}
// NOTE: Photo์˜ ๊ธฐ๋ณธ className๊ณผ ์ถฉ๋Œ๋‚˜์„œ css props๋กœ ์ž‘์„ฑ
css={{ position: "absolute", inset: 0, filter: "brightness(.5)" }}
loader={cloudinaryLoader}
loading="eager"
sizes="100px"
src={imageSrc}
Expand Down
2 changes: 1 addition & 1 deletion src/features/upload/components/UploadMeme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const UploadMeme = ({ src, isFocus }: Props) => {
setHeight(h);
}}
>
<Photo className="mx-16 rounded-16" src={src} />
<Photo unoptimized alt="์—…๋กœ๋“œ ์ด๋ฏธ์ง€" className="mx-16 rounded-16" src={src} />
<UploadMemeData
className="max-h-[100rem] overflow-hidden transition-[max-height] duration-500 ease-in-out group-[:not(:focus-within)]:max-h-0"
css={{ maxHeight: height / 10 + "rem" }}
Expand Down
11 changes: 8 additions & 3 deletions src/features/upload/components/UploadMemeData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ export const UploadMemeData = ({ src, className }: Props) => {
return (
<div className={`flex flex-col gap-24 pt-24 pb-6 ${className}`}>
<div className="flex items-center gap-8 py-8 px-16">
<Photo className="h-24 w-24 rounded-12 bg-gray-300" src={src} />
<Photo
unoptimized
alt="์ธ๋„ค์ผ"
className="h-24 w-24 rounded-12 bg-gray-300"
src={src || ""}
/>
<span className="text-14-semibold-140 text-gray-900">๋ถ„๋…ธํ•˜๋Š” ISTJ</span>
</div>
<div className="relative w-full px-16 text-18-semibold-140 leading-[160%]">
Expand All @@ -27,7 +32,7 @@ export const UploadMemeData = ({ src, className }: Props) => {
<Item value="๋ฐˆ ์ถœ์ฒ˜">
<Header className="border-b border-gray-100">
<Trigger className="flex w-full items-center justify-between gap-8 rounded-full px-24 py-16 text-16-semibold-140 [&>span>#chevronDown]:data-[state=open]:rotate-180">
<Photo className="h-24 w-24 rounded-12" />
<Photo unoptimized alt="๋ฐˆ ์ถœ์ฒ˜" className="h-24 w-24 rounded-12" src="" />
<span className="flex-grow text-left text-16-semibold-140">๋ฐˆ ์ถœ์ฒ˜</span>
<span className="flex h-24 w-24 items-center justify-center rounded-full hover:bg-gray-100">
<Icon
Expand Down Expand Up @@ -58,7 +63,7 @@ export const UploadMemeData = ({ src, className }: Props) => {
<Item value="๋ฐˆ ์‚ฌ์šฉ์ƒํ™ฉ">
<Header className="border-b border-gray-100">
<Trigger className="flex w-full items-center justify-between gap-8 rounded-full px-24 py-16 text-16-semibold-140 [&>span>#chevronDown]:data-[state=open]:rotate-180">
<Photo className="h-24 w-24 rounded-12" />
<Photo unoptimized alt="๋ฐˆ ์‚ฌ์šฉ์ƒํ™ฉ" className="h-24 w-24 rounded-12" src="" />
<span className="flex-grow text-left text-16-semibold-140">๋ฐˆ ์‚ฌ์šฉ์ƒํ™ฉ</span>
<span className="flex h-24 w-24 items-center justify-center rounded-full hover:bg-gray-100">
<Icon
Expand Down
9 changes: 8 additions & 1 deletion src/pages/mypage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudinaryLoader } from "config/cloudinary-loader";
import Link from "next/link";
import { css } from "twin.macro";

Expand All @@ -19,7 +20,13 @@ const MyPage = () => {
<>
<MyPageNavigation />
<div className="flex flex-col items-center justify-center py-40 font-suit">
<Photo className="h-100 w-100 rounded-full" src={user.imageUrl} />
<Photo
alt={`${user.name || ""}์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€`}
className="h-100 w-100 rounded-full"
loader={cloudinaryLoader}
sizes="100px"
src={user.imageUrl}
/>
<span className="mt-4 text-22-bold-140">
{isLoading ? <Skeleton animation="wave" width={70} /> : user.name}
</span>
Expand Down
Loading
Loading