Skip to content

A blog and personal portfolio website where I record and share various topics and insights related to development - from books, courses, seminars, and more - using Next.js, TypeScript, SCSS, and multilingual services.

Notifications You must be signed in to change notification settings

Doyu-Lee/portfolio_doyu

Repository files navigation

DOYU's Blog v1.1.0


Next.js의 App Router와 TypeScript, SCSS 등으로 개발과 관련하여 관심있는 주제를 스크랩하고 책, 강의, 세미나 등에서 새롭게 알게된 것들을 기록하는 등 일부 다국적 서비스가 지원되는 블로그 겸 개인 포트폴리오 사이트입니다. Vercel로 배포되었습니다.


한국어 페이지 링크 | 영어 페이지 링크

-> 둘 중 한 링크로 들어가셔서 오른쪽 상단의 언어 버튼을 누르셔도 한/영 전환이 됩니다.

v1.1

히스토리

[v1.0] 블로그v1.0



🧐 기술 스택

TypeScript Badge React Badge Next.js Badge SCSS Badge

Husky Badge Styled-lint Badgelint-staged Badge Prettier Badge ESLint Badge

Vercel Badge

이번 프로젝트에서 SCSS를 선택한 이유 ( 👈🏼 클릭!)
  • 특히 SCSS를 선택한 이유는 앞서 사용해본 Styled-Component와 Emotion과 같은 CSS-in-JS는 런타임에서 스타일 직렬화가 일어나기 때문에 어느정도 런타임 비용이 든다는 문제가 있기 때문이었습니다.
  • CSS-in-JS 방식 중에서도 런타임 비용이 들지 않는 Vanilla Extract 등의 라이브러리 경우에도 결국 CSS-in-JS의 특징인 컴포넌트가 처음 마운트 될 때 스타일이 계속 삽입되어 브라우저가 모든 DOM 노드에서 스타일이 다시 계산된다는 한계가 있습니다.
  • 결론적으로 CSS-in-JS를 쓰는 이유 중 하나인 스타일이 지역 스코프라는 점과, CSS 파일이 해당 컴포넌트와 같은 위치에 배치된다는 것은 CSS 모듈로 해결할 수 있고, CSS 모듈에서 코드 중복의 단점은 SCSS를 사용하여 mixin 변수활용으로 해결했습니다.

⌨️ 로컬 실행 방법

1. 환경 변수 설정

해당 레포지토리를 clone하셔서 여신 후, 노션 API와 관련하여 아래 링크를 참고하셔서 환경변수를 설정하시면 됩니다.

NOTION_PAGE_ID={노션 페이지 아이디}
NOTION_TOKEN_V2={노션 토큰 값}

2. 패키지매니저로 로컬 설치 및 개발모드로 실행

그 뒤, 로컬에서 실행을 할 수 있습니다. 프로젝트는 pnpm으로 관리됩니다.

pnpm install
pnpm dev

🌟 구현 기능

목록 (상세 설명에는 생략된 내용 포함)

v1.0

  • 다국적 언어 지원
    • 렌더링 방식에 따른 한/영 변환 기능
    • JEST 동적 라우팅 테스트
  • 최적화
    • next.js 내장기능을 사용한 최적화
      • next/font
      • next/dynamic
  • 노션API
    • 노션 API를 활용한 페이지 연동
    • 미들웨어를 활용한 리다이렉션 설정
  • 인터랙티브
    • 3D 카드 효과 추가 (useRef의 데이터 저장 로직, useLayoutEffect)
    • 타이핑 효과 애니메이션 컴포넌트 및 영/한 데이터 추가
    • useRef 배열로 관리하며 도미노 글자 애니메이션 직접 구현
  • 기타
    • 페이지 반응형 적용

v1.1

  • generateMetadata를 이용한 SSR 메타 태그 적용
    • SSR로 동적 메타 태그 생성
    • 한/영 언어별(동적) 정적 메타 태그 생성
  • 사용자 편의를 위해 상세 페이지 스크롤 기능 추가
    • 스크롤 게이지바 UI
    • 특정 스크롤 바의 위치를 클릭시 해당 페이지 위치로 이동
    • 클릭 후 드래그하여 동시에 페이지 실시간 이동
  • 기타
    • 공통 버튼 컴포넌트에 disabled 속성 추가

v1.0 상세설명

- 다국적 언어 지원


1. 렌더링 방식에 따른 한/영 변환 기능

  • 지원하는 언어 별 json 데이터 생성
  • SSR / CSR용 useTranslation 훅 개별 생성
  • 페이지마다 params 및 URL 추출 로직을 추가
  • en/roadmap에서 언어 전환 버튼을 눌렀을 때, ko가 아니라 ko/roadmap으로 이동하도록 함.
  • 헤더에서 한/영 변환에 따른 폰트를 개별적으로 적용

2. JEST 동적 라우팅 테스트

  • 전역에 jest react-i18next 모듈 추가
  • 동적 라우팅에 따라 라우팅 테스트 수정

- 최적화


1. next.js 내장기능을 사용한 최적화

  • next/link, next/dynamic, next/font, next/image 등을 이용한 성능 최적화
[next/font]
  • 폰트의 경우 아래와 같이 전역 변수로 등록하여 사용
import {
  Space_Mono, // Google font의 Space Mono 같이 띄어쓰기가 되어있는 폰트명은 언더바 사용 
...
} from 'next/font/google';

export const spaceMono = Space_Mono({
  subsets: ['latin'],
  weight: '400',
  variable: '--font-spaceMono',  // 전역변수로 등록
});
// 최상위 layout.tsx
import {
...
  spaceMono,
} from '../../../public/fonts/fonts';


export default function RootLayout({ children, params: { lng } }: RootLayoutProps) {
  const fontVariables = `
...
  ${spaceMono.variable} 
`;

  return (
    <html lang={lng} dir={dir(lng)} className={fontVariables}> // html 태그에 className으로 넣어준 뒤 사용

next/font/google에 내장되어 있는 영어 폰트에 한하여 빌드타임에 미리 로컬에 폰트를 저장할 수 있기 때문에 영문 폰트와 관련된 layout shift를 최소화하여 성능을 최적화하였습니다. 외부에서 가져온 한글 폰트는 로딩 컴포넌트를 삽입하여 로드되기 전 레이아웃이 깨지는 현상을 막았습니다.

[next/dynamic]

preload 될 필요가 없는 컴포넌트는 Lazy Loading으로 네트워크 비용을 절감시키고자 했습니다.

const ContactArticle = dynamic(() => import('@/components/contacts/ContactArticle'), {
  ssr: false,
});

- 노션API


1. 노션 API를 활용한 페이지 연동


2. 미들웨어를 활용한 리다이렉션 설정

  • 리다이렉션 이슈를 미들웨어를 활용하여 해결

현재 웹사이트는 다국어 지원으로 /ko 또는 /en과 같이 지원 언어 데이터 값이 경로에 포함이 됩니다. 문제는 노션 페이지 개별 게시글을 입력할 경우 자동으로 /{페이지 값}으로 이동한다는 것이었습니다.

  • useRouter을 쓸 수 없는 SSR 메인 페이지였고, SSR에서는 리다이렉션 기능이 지원되지 않음을 확인했습니다.
  • 따라서 react.config에서 redirection 설정을 하고자 했으나 /{페이지값} -> /ko/{페이지값} 이렇게 동적 언어 데이터가 아닌 특정 데이터 값을 입력해줘야 했고, 그후 다시 홈 버튼을 누르면 /ko/{페이지값}/ko 등으로 나오는 사이드 이펙트가 있었습니다.
  • app 폴더 동위에 middleware를 생성하여 redirection 시키는 것으로 해결했습니다.

- 인터랙티브


1. 3D 카드 효과 추가

  • transform-style: preserve-3d 속성을 활용
  • 데이터가 변동되면 화면이 리렌더링되는 useState 대신 useRef를 사용하여 데이터 변경
  • useLayoutEffect를 이용하여 컴포넌트가 렌더링되기 전에 동기적으로 애니메이션 이벤트 등록 및 함수 실행
  • 애니메이션 최적화 API requestAnimationFrame() 적용

2. 타이핑 효과 애니메이션 컴포넌트 및 영/한 데이터 추가

  • react-typist 라이브러리를 사용했지만 최신 React 18버전 이상에서 호환되지 않는 일부 성능 문제가 발생
  • 해당 라이브러리 레포지토리 이슈에서 관련 문제 발견 후, 2022년 초부터 업데이트가 안 되고 있다는 것을 확인
  • react-simple-typist로 라이브러리 교체 후 이상없이 작동

3. useRef 배열로 관리하며 도미노 글자 애니메이션 직접 구현

  • span을 생성하는 useEffect, 해당 span에 시간차로 css를 적용하는 useEffect로 도미노처럼 차례대로 쓰러지는 듯한 글자 애니메이션을 적용
  • useEffect 안에서 useRef과 같은 훅 사용이 불가능하기 때문에 useEffect 안에서 각 글자 데이터들이 map 함수에서 span 태그를 생성하는 로직을 짤 때 createRef를 사용하였지만 추후 함수 컴포넌트 방식에 맞게 useRef를 배열로 선언해준 다음 useEffect 안의 map 함수에서 해당 배열에 span 태그와 ref 값을 차례로 할당시키는 방법으로 리팩토링
  const [childRef, setChildRef] = useState<React.JSX.Element[]>([]);
  const spanRefs = useRef<null[] | HTMLSpanElement[]>([]);
  ...
  useEffect(() => {
    ...
      letters.map((letter, index) => {  // 예 ) letters =  '망고'.split('');
        const newSpan = (
          <span
            key={Math.random()}
            ref={(el) => {
              spanRef.current[index] = el;
            }}
          >
            {letter}
          </span>
        );
        return setChildRef((prev) => [...prev, newSpan]);
      });
    }

    return () => setChildRef([]);
  }, [titleLetters]);

  ...
    return (
          <div>
            {childRef}  // 예) <span>망</span> <span>고</span>  
          </div>

- 기타


1. 페이지 반응형 적용

  • 예상하는 사용자 접속 경로는 웹이지만, 갤럭시 폴드 (min-width : 280px)까지 반응형 적용

- 화면

블로그


v1.1 상세설명

- generateMetadata를 이용한 SSR 메타 태그 적용


1. SSR로 동적 메타 태그 생성

type Props = {
  params: { pageId: string }; // params에서 현재 pageId 추출 
};

export const generateMetadata = async ({
  params: { pageId },
}: Props): Promise<Metadata> => {
  const recordMap = await notion.getPage(pageId); 
  const title = getPageTitle(recordMap);   // 해당 pageId의 제목 데이터 가져오기

  return {
    title,
    openGraph: {
      title,
    },
  };
};

2. SSR로 한/영 언어별(동적) 정적 메타 태그 생성

type Props = {
  params: { lng: string }; // params 에서 언어 상태 추출 
};

export const generateMetadata = async ({ params: { lng } }: Props): Promise<Metadata> => {
  // 언어 상태에 따른 정적 메타 데이터 생성
  return lng === 'ko' ? homeMetaData.metadataKO : homeMetaData.metadataEN; 
};

- 사용자 편의를 위해 상세 페이지 스크롤 기능 추가


1. 스크롤 게이지바 UI 출력

  • useRef를 사용하여 전체 브라우저의 높이에서 100vh를 뺀 후, scrollTop 위치를 구하여 비율 계산

2. 클릭 후 드래그하여 동시에 페이지 실시간 이동

  • mousedown, mousemove, click 이벤트를 이용하여 이벤트가 일어난 순서대로 events라는 변수를 useState로 상태 관리
  • click 했을 때 바로 해당 클릭된 위치로 스크롤 이동
  • click -> mousedown -> mousemove 가 일어난 경우 역시 실시간으로 마우스 위치로 스크롤 동기화


About

A blog and personal portfolio website where I record and share various topics and insights related to development - from books, courses, seminars, and more - using Next.js, TypeScript, SCSS, and multilingual services.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published