프로젝트 개발을 하면서, 구현을 해야하는 상황은 다음과 같았다.

1. `video`를 넣는다
2. 배경색을 비디오의 배경색과 동일하게 설정한다.

예상했던 결과는 다음과 같다.

원했던 이미지

 

`video`의 배경색을 추출해서 전체 배경의 `background-color` 속성에 적용하였다.

최종 디자인을 마무리하고 배포만을 남겨둔 시점에서 사촌누나에게 자랑하려고 폰으로 접속하는 순간, 이미지가 다음과 같이 나타났다.

위아래 색이 다른게 느껴지시겠죠?

 

😱 좌절했다 😱  

너무 당황스러웠다. 웹 개발의 장점은 작업물을 사람들에게 보여줄 수 있다는 점이다.

거하게 칭찬받고 이를 다시 추진력으로 나아가려고 했지만, 보여주려고 접속하는 순간 너무 당황스러웠다.

만들다 포기한 것 같은 페이지가 보이는 것이다.

(잘 보면, 위 아래 색이 다르다)

 

서론이 너무 길었다.


✏️ 문제 분석

나는 먼저, `video`의 색을 의심하였다. 하지만, 색을 추출해본 결과 코드로 입력했던 색과 일치했다.

그렇다면, 배경색이 문제다.

 

개발자의 역량은 빠르고 정확한 구글링. 바로 구글링하였다.

Stackoverflow에서 비슷한 문제를 찾아서 원인을 분석한 결과 다음과 같다.

가능한 원인
1. 그래픽 카드와 같은 기기의 하드웨어가 달라서 생기는 문제이다.
2. 브라우저의 색 설정이 달라서 생기는 문제이다.

 

결국, 원인이 무엇이든 나는 브라우저 안에서 해결해야 했다.

모든 사람들이 같은 기기를 쓰는 날이 왔으면......

 


💡해결 방법

내가 해결한 방법은 다음과 같다.

1. `video`에서 내가 원하는 배경색이 있는 곳을 알아낸다. (시점, 지점)
2. 해당 색으로 된 `canvas`를 그린다.
3. 해당 `canvas`의 이미지를 전체 배경에 적용한다. (배경색 입히기)

 

코드를 보면 바로 이해가 될 것이다.

// javascript 파일
function isColorInRange(expectedColor, givenColor) {
    const THRESHOLD = 40;
    for (var i = 0; i < 3; i++) {
        if (((expectedColor[i] - THRESHOLD) > givenColor[i]) || ((expectedColor[i] + THRESHOLD) < givenColor[i])) {
            return false;
        }
    }
    return true;
}

function setVideoBgColor(vid, nativeColor) {
    if (vid) {
        let vidBg = document.querySelector('.large_bg');
        if (vidBg) {
            // draw first pixel of video to a canvas
            // then get pixel color from that canvas
            let canvas = document.createElement("canvas");  // 배경색을 칠할 요소를 선택하기!!!
            canvas.width = 1;
            canvas.height = 1;
            let ctx = canvas.getContext("2d");
            ctx.drawImage(vid, 0, 0, 1, 1);

            let p = ctx.getImageData(0, 0, 1, 1).data;
            //console.log("rgb(" + p[0] + "," + p[1] + "," + p[2] + ")");
            if (isColorInRange(nativeColor, p)) {        
                vidBg.style.backgroundColor = "rgb(" + p[0] + "," + p[1] + "," + p[2] + ")";
            }
        }
    }
}

function setVideoBgColorDelayed(vid, nativeColor) {
    console.log("실행됨");
    setVideoBgColor(vid,nativeColor)            
}

 

적용할 `video`의 `onPlay` 속성에 다음과 같이 적용하면 된다.

<video muted autoplay loop playsinline onplay="setVideoBgColorDelayed(this,[214,169,216])"> // 원하는 색을 두번째 인자로 넣기
    <source src="/video/main_video.mp4" type="video/mp4">                                    
</video>

 

해당 코드는 라이브러리의 도움이 없으므로, `video` 태그가 생성된 이후에 적용되어야 한다는 점을 주의하자.

`defer` 속성이 `script`에 필요할 수 있다.

 

 

험난한 개발인생 하지만 오늘도 오류 해결~

사촌누나 다시 보여줘야지..

💢 발생한 문제

`"use client"`를 사용하는 컴포넌트에서, 데이터를 서버에 POST 하고 나서, 데이터들이 리스트형태로 되돌아가는 과정에서 최신 데이터가 반영되게 하려면 어떻게 해야할까??

 

Next.js 공식문서는 다음과 같은 상황에 2가지 해결책을 제시한다.

Cached data can be revalidated in two ways

[Time-based revalidation]
Automatically revalidate data after a certain amount of time has passed.
This is useful for data that changes infrequently and freshness is not as critical.

[On-demand revalidation]
Manually revalidate data based on an event (e.g. form submission). On-demand revalidation can use a tag-based or path-based approach to revalidate groups of data at once. This is useful when you want to ensure the latest data is shown as soon as possible (e.g. when content from your headless CMS is updated).

 

Time-based revalidation은 일정 시간마다 캐싱된 값들을 다시 만들도록 하는 것이다.

처음에 이렇게 만들었다가, 클라이언트분께서 Form을 작성한 이후에 다른 탭을 갔다 와야 저장이 된다고 해결해달라고 하셨다. 

개발자가 아닌 분들에게 하나하나 설명드릴 필요도 없고, 그분들도 알 필요 없다고 생각한다.

그래서, 이를 근본적으로 해결하는 방법으로 On-demand revalidation을 사용하기로 했다.

 

 

✏️ First Solution

1. server component fetch function에 revalidate tag를 달아준다.

const fetchData = async (): Promise<{
  data: NoticeType[];
  totalPages: number;
}> => {
  try {
    const res = await fetch(`${baseUrl}/api/getNotice`, {
      next: { tags: ["notice"] }, // Revalidate Tag
    });

    const { data, totalPages } = await res.json();

    return { data: data, totalPages: totalPages };
  } catch (error) {
    console.log(error);
  }
  return { data: [], totalPages: 0 };
};

 

2. Form을 제출하는 Client 컴포넌트에 `"next/cahce"`에서의 `revalidateTag` 함수를 실행시킨다.

  // Form Submit
  const submitHandler = async (isImp: boolean): Promise<undefined> => {
    try {
      // 데이터 만들기
      const data: NoticeType = {
        ...noticeInput,
        contents: contents,
        timeStamp: new Date(),
        important: isImp,
      };
      setNotices(data);      
      
      customRevalidateTag("notice"); // On-Demand revalidation
      
      const nextPath: Path = "/notification?page=1";
      router.push(nextPath);
    } catch (error) {
      console.log("Error Occured on Submitting!", error);
    }
  };

 

💢 Problem

하지만, 문제가 발생했다. revalidateTag는 서버에서 실행하는 코드이므로, "use client"를 사용한 컴포넌트 즉, Form이 있는 컴포넌트에서는 사용할 수 없다.

 

그래서, `revalidateTag`만 사용하는 함수를 분리하고, `import`하여 사용하였다.

 

최종 Solution

// customRevaliateTag.ts
"use server";
import { revalidateTag } from "next/cache";

export async function customRevalidateTag(tag: string) {
  console.log("revalidating");

  revalidateTag(tag);
}
// Form Component

  const submitHandler = async (isImp: boolean): Promise<undefined> => {
    try {
      const data: NoticeType = {
        ...noticeInput,
        contents: contents,
        timeStamp: new Date(),
        important: isImp,
      };
      setNotices(data);      
      
      customRevalidateTag("notice"); // 새로 만든 On-Demand Function
      
      const nextPath: Path = "/notification?page=1";
      router.push(nextPath);
    } catch (error) {
      console.log("Error Occured on Submitting!", error);
    }
  };

 

참고

추가로, 나는 build해서 실행했을때, `revalidate`을 통해 Caching을 다시하는 와중에 `router.push`가 되는 것을 방지하고자, `setTimeout`을 실행시켜주었다.

이때, async Function안에서 setTimeout을 연속적으로 실행시키기 위해 새로 함수를 정의하여서 사용하였다.

 

해당 함수를 이 글을 참고해주세요.

2024.04.30 - [웹 프로그래밍/JavaScript] - async, await + setTimeout 동시에 사용하기

const submitHandler = async (isImp: boolean): Promise<undefined> => {
    try {
      const data: NoticeType = {
        ...noticeInput,
        contents: contents,
        timeStamp: new Date(),
        important: isImp,
      };
      setNotices(data);
      
      setIsSaving(true); // 저장 중임을 표시하기 위해
      customRevalidateTag("notice");
      await delayTimeout(4000); // Promise Timer 함수
      
      const nextPath: Path = "/notification?page=1";
      router.push(nextPath);
    } catch (error) {
      console.log("Error Occured on Submitting!", error);
    }
  };

 

 

참고자료

https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating

 

Data Fetching: Fetching, Caching, and Revalidating | Next.js

Learn how to fetch, cache, and revalidate data in your Next.js application.

nextjs.org

https://github.com/vercel/next.js/discussions/58600

 

'웹 프로그래밍 > Next.js' 카테고리의 다른 글

[Next.js] CSR, SSG, SSR 제대로 알자  (4) 2024.03.24

개발을 하다보면, 순서대로 코드를 실행시켜야 할 상황이 있다.

 

나 같은 경우에는, Firebase에 데이터를 업로드하는 과정에서 데이터를 업로드 한 뒤, 해당 데이터를 제외한 나머지를 삭제하기 위해 순서를 지켜야 했음.

 

하지만, 다음과 같이 코딩을 해보면 순서대로 실행되지 않는 머리아픈 상황이 발생한다.

const updateFiles = async () => {
    // #1. 파일 업데이트 코드
    
    // #2. 딜레이 코드
    setTimeout(()=>{
    	console.log("Delay~");
    }, 3_000);
	
    // #3. 파일 삭제 코드
};

 

실행결과, #1 코드와 #3 코드가 순차적으로 실행된 뒤, #2 코드가 실행된다.

 

✏️ setTimeout은 Promise를 반환하지 않기 때문이다.

따라서, 기능은 동일하지만 Promise를 반환하는 함수를 만들어서, 사용하면 된다!

const promiseDelay = (delay: number): Promise<undefined> => {
	return new Promise((resolve) => setTimeout(resolve, delay));
}

const fetchData = async () => {
    // #1. 파일 업데이트 코드
	
    // #2. 딜레이 코드
    await promiseDelay(3_000)	    
	
    // #3. 파일 삭제 코드
};

 

 

참고

- 숫자를 입력할때, JavaScript 안에서는 _를 마음대로 입력해도 됩니다.

ex) 1000_0000 (가능), 10_000_000 (가능)

+ Recent posts