Skip to content

chaehaeun/my-portfolio

Repository files navigation

🏖 포트폴리오 사이트 프로젝트

포트폴리오메인

피그마시안

개인 포트폴리오 웹 구현 프로젝트입니다.


🪧 목차


📝 프로젝트 정보


프로젝트: 개인 포트폴리오 웹사이트

기획, 디자인, 제작: 채하은

분류: 개인 프로젝트

제작 기간: project_start project_end

배포일: 2023.06.01

주요 기능:

  • 다크모드
  • 반응형 웹
  • About / Project 데이터 동적 생성
  • 프로젝트 더보기 기능


🚀 배포 링크

https://portfolio-49c62.web.app/

🛠 사용 기술 및 도구


🔨 업데이트

  • [2023.06.01] 사이트 배포

  • [2023.07.21] 접근성 향상 (태그 명암비 수정)

  • [2023.08.23] 사이트 성능/seo 개선

  • [2023.09.01] 프로젝트 더보기 기능 추가


🎨 기능 구현

1. 다크 모드

다크모드

const [toggle, setToggle] = useState('Dark')

  const toggleDarkMode = () => {
    if (localStorage.getItem('theme') === 'dark') {
      localStorage.removeItem('theme')
      document.documentElement.classList.remove('dark')
      setToggle('Light')
    } else {
      document.documentElement.classList.add('dark')
      localStorage.setItem('theme', 'dark')
      setToggle('Dark')
    }
  }

  useEffect(() => {
    if (localStorage.getItem('theme') === 'dark') {
      document.documentElement.classList.add('dark')
      setToggle('Dark')
    } else {
      setToggle('Light')
    }
  }, [])

2. 반응형 웹

반응형 웹

tailwind css 기능을 사용해 sm(640px) md(768px) lg(1024px) xl(1280px) 을 기준으로 브레이크포인트를 잡았다.

3. About / Project 데이터 동적 생성

파이어스토어

List.tsx 컴포넌트 👉

projects.tsx 컴포넌트👉

컴포넌트 재사용이라는 리액트의 특징을 살리기 위해 firestore에 데이터를 저장한 뒤 동적으로 컴포넌트를 생성. firestore에 데이터 값을 입력해 넣으면 동적으로 데이터가 뿌려지게 된다.

같은 컴포넌트를 사용하면서도 데이터들의 타입에 따라 li 스타일에 차이를 둘 수 있도록 코드를 작성했다.

4. 프로젝트 더보기 기능

export const getProjectData = async (
  itemsPerPage: number,
  lastDoc: QueryDocumentSnapshot<DocumentData> | null,
) => {
  const projectQuery = query(
    collection(dbService, 'project'),
    orderBy('id', 'desc'),
    ...(lastDoc ? [startAfter(lastDoc)] : []),
    limit(itemsPerPage),
  )

  const querySnapshot = await getDocs(projectQuery)

  const lastDocument = querySnapshot.docs.length
    ? querySnapshot.docs[querySnapshot.docs.length - 1]
    : null

  const dataQuery = querySnapshot.docs.map(doc => doc.data() as ProjectDataType)

  return {
    data: dataQuery,
    lastDocument,
  }
}

/// 

  const [lastDoc, setLastDoc] = useState<LastDoc>(null)
  const [hasMoreData, setHasMoreData] = useState<boolean>(true)

  const loadMoreData = async () => {
    try {
      if (!hasMoreData) return
      const result = await getProjectData(ITEMS_PER_PAGE, lastDoc)

      if (result.data.length < ITEMS_PER_PAGE) {
        setHasMoreData(false)
      }
      setProjectData(prevData => [...prevData, ...result.data])
      setLastDoc(result.lastDocument)
    } catch (error) {
      openModal('데이터를 불러오는데 실패했습니다.')
    }
  }

한 번에 몇 개의 데이터를 보여줄지를 정하는 ITEMS_PER_PAGE와 불러온 데이터의 마지막 데이터를 의미하는 lastDoc를 인자로 받아 getProjectData 함수를 호출하고, 만약 불러온 데이터의 개수가 ITEMS_PER_PAGE보다 작다면 hasMoreData를 false로 변경해 더 이상 데이터를 불러오지 않도록 설정했다. 그리고 hasMoreData가 true일 때만 loadMoreData 함수를 호출하도록 설정했다.


🩹 트러블슈팅

1. 타입에러 - 임시로 지정해놓은 데이터 타입 any를 수정하는 과정에서 무한 타입에러와 마주함.
const [aboutData, setAboutData] = useState<any>([])

const [projectData, setProjectData] = useState<DocumentData[]>([]); // DocumentData[]는 firestore 자체에서 지원하는 타입

로 수정했으나,

'(data: DataType, index: number) => JSX.Element' 형식의 인수는 '(value: DocumentData, index: number, array: DocumentData[]) => Element' 형식의 매개 변수에 할당될 수 없습니다. 'data' 및 'value' 매개 변수의 형식이 호환되지 않습니다. 'DocumentData' 형식에 'DataType' 형식의 projects, date, description, techStack 외 2개 속성이 없습니다.ts(2345) (parameter) data: DataType

라는 에러와 함께 여전히 문제가 해결되지 않았다.

원인을 찾아본 결과 DocumentData 형식은 DataType의 필수 속성인 projects, date, description, techStack 외에 다른 속성을 갖지 않기 때문이었다.

interface DataType extends DocumentData {
  projects: string
  date: string
  description: string
  techStack: string[]
  tag: string[]
  github?: string
  notion?: string
  imgURL: string
  deploy?: string
} // 

DataType를 DocumentData를 상속하도록 확장하여 DataType이 DocumentData의 모든 속성을 포함하면서 추가적인 속성을 정의할 수 있게 변경했다.

const querySnapshot = await getDocs(projectQuery)
const dataQuery = querySnapshot.docs.map(doc => doc.data() as DataType)

또, dataQuery에서 doc.data()를 as DataType로 형변환하여 타입을 맞추어 data prop으로 사용되는 데이터의 타입을 DataType로 일치시켰다.


💫 성능 개선 작업

개선 전

성능 개선 전

개선 후

성능 개선 후

Vite는 프로덕션 빌드를 위해 기본적으로 특정 형태의 번들링을 하지 않기 때문에 별도로 이미지 최적화와 텍스트 압축을 통해 성능 개선 작업이 필요함을 깨달았다.

텍스트 압축 / 이미지 최적화

  plugins: [
    //...
    compression(),
    viteImagemin({
      plugins: {
        jpg: imageminMozjpeg(),
        png: imageminPngQuant(),
        gif: imageminGifSicle(),
        svg: imageminSvgo(),
      },
      makeWebp: {
        plugins: {
          jpg: imageminWebp(),
          png: imageminWebp(),
        },
      },
    }),
  ],
  • vite-plugin-compression2 : Gzip 및 Brotli 압축 알고리즘을 지원하는 Vite 플러그인
  • viteImagemin : 이미지 최적화 플러그인

이 두 플러그인을 사용해 텍스트 압축과 이미지 최적화를 진행했다.


💡 추후 업데이트 할(원하는) 기능

  • 로딩스피너(스켈레톤)
  • 프로젝트 filter 기능 (팀 프로젝트 / 개인 프로젝트 / 클론)
  • 이력서 / 프로젝트 상세페이지 동적 라우팅
  • seo 최적화

About

포트폴리오 웹사이트 프로젝트

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published