DOTORI 회고록

유환빈·2023년 4월 11일
9

프로젝트

목록 보기
2/3
post-thumbnail

DOTORI 란?

Dormitory(기숙사) 줄임말인 Dotori 프로젝트는 기숙사 자습신청 등 잡다한 것들이 모두 사감 선생님의 수기로 작성되어야 한다는 점에서 불편함을 느끼게 되어서
기숙사의 자습신청, 안마의자 사용, 규정 위반, 기상음악 신청 등을 웹사이트로 간편하게 관리 할 수 있도록 만든 웹사이트다.


DOTORI-v2 ?

기존에 만들어진 도토리는 디자이너가 따로없어서 사용자가 디자인이 아쉽다는 평이 많았고 파일구조도 제대로 짜고 만든게 아니라 잘 구조화되지 않았고 인증로직도 제대로 짜여져 있지 않는등 여러가지 문제로 인해 도토리 v2 를 내기로 했다.
이번에 v2에는 디자인변경, 다크모드, Next.js 로 migration, 사감쌤과 1대1문의 기능등을 추가하기로 했다.


구성인원

Frontend : 3명, Backend: 3명, Android: 4명, IOS: 3명, Design: 1명으로 구성되어있고
Frontend Leader 역할을 맡아 프로젝트 초기설정과 개발의 전반적인 리드를 했다.


사용한 기술

Atomic 디자인 패턴

기존 dotori 프로젝트 파일구조는 component라고 해놓고 너무 중구난방 느낌으로 코드를 짜놔서 처음부터 잘 구조화하면 좋을 것 같다는 선배의 조언을 바탕으로 v2 는 파일구조를 어떻게 할까 많이 생각해봤다.
그러다가 파일을 구조단위로 나눠서 관리하는 Atomic 디자인 패턴을 보게되어서
components 안에 페이지단위로 폴더를생성하고 각 폴더안에 atoms, molecules, organisms,templates 폴더를 만들어서 관리하고 common 폴더를 만들어서 공통으로 사용되는 컴포넌트관리를 하는 방식으로 하기로 했다.

SSR + SWR

SSR 은 초기 HTML 데이터를 빠르게 받아와 사용자가 빠르게 화면구조를 볼 수 있고 SEO가 좋다는 장점이 있지만
서버는 항상 각 요청이 올때마다 HTML파일을 생성하기 때문에 컨텐츠 캐시가 되지 않아 서버 부화가 걸릴 수 가있고 TTV와 TTI 간에 시간 간격이 존재하고 매번 페이지를 요청할 때마다 새로 고침 되는 단점이 있다.
그래서 SWR 을 같이사용해 초기에 SSR로 가지고온데이터를 SWRConfig 로 캐싱해서
기존에 CSR 의 장점인 서버부화를 감소시키고 TTV와 TTI 간에 시간 간격이 없게 했다.

또 SWR 의 기능을 이용해서 페이지에 다시 포커스했을때나 자습신청시간(8시)과 안마의자 신청시간(8시 20분)이 되면 자동으로 재랜더링이 되게 설정했다.

ex) home.tsx

const HomePage: NextPage<{
  fallback: Record<string, myProfileType> &
    Record<string, noticePageProps> &
    Record<string, applyPageProps> &
    Record<string, applyPageProps>;
}> = ({ fallback }) => {
  UseThemeEffect();
  return (
    <>
      <SEOHead title={'| 홈페이지'} />
      <SWRConfig value={fallback}>
        <MainTemplates>
          <SideBar />
          <HomeTemplates>
            <TimeBoard />
            <Profile />
            <MealBoard />
            <NoticeBoard />
            <SelfStudyBoard />
            <MassageBoard />
          </HomeTemplates>
        </MainTemplates>
      </SWRConfig>
      <ChannelBtn />
    </>
  );
};

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { Authorization } = await getToken(ctx);
  const role = getRole(ctx);
  const header = {
    headers: { Authorization },
  };

  const promises = [
    apiClient.get(MemberController.myProfile, header),
    apiClient.get(NoticeController.getNotice(role), header),
    apiClient.get(SelfstudyController.selfStudyInfo(role), header),
    apiClient.get(MassageController.massage(role), header),
  ];

  try {
    const [
      { data: myData },
      { data: noticeData },
      { data: selfStudyData },
      { data: massageData },
    ] = await Promise.all(promises);

    return {
      props: {
        fallback: {
          [MemberController.myProfile]: myData,
          [NoticeController.getNotice(role)]: noticeData,
          [SelfstudyController.selfStudyInfo(role)]: selfStudyData,
          [MassageController.massage(role)]: massageData,
        },
      },
    };
  } catch (e) {
    return { props: {} };
  }
};

export default HomePage;

Middleware

미들웨어는 request가 완료되기 전에 코드를 실행해준다.
따라서 로그인 여부에따른 url 직접접근설정을 middleware 에 처리해 한파일에 가능하게 헀다.
(로그인이 되있는데 인증페이지로 이동하려고할때, 로그인이 안되있는데 인증페이지 이외의 페이지로 이동하려고할때)

ex) middleware.ts

import { NextRequest, NextResponse, userAgent } from 'next/server';

export const middleware = async (req: NextRequest) => {
  const ua = userAgent(req);
  const RefreshToken = req.cookies.get('RefreshToken');
  const isAuthPage = ['/signin', '/signup', '/'].some(
    (i) => i == req.nextUrl.pathname
  );
  
  if (ua.isBot) {
    return NextResponse.redirect(new URL('/signin', req.url));
  }

  if (RefreshToken && isAuthPage) {
    return NextResponse.redirect(new URL('/home', req.url));
  } else if (!RefreshToken && !isAuthPage) {
    return NextResponse.redirect(new URL('/signin', req.url));
  }

};

export const config = {
  matcher: [
    '/signin',
    '/signup',
    '/home',
    '/selfstudy',
    '/massage',
    '/penalty',
    '/notice',
    '/notice/:path*',
    '/',
  ],
};

Dynamic Import

next.js 기본적으로 모든 페이지를 pre-rendering 한다.
웹 페이지에서는 프로모션 페이지같은 큰 JS 파일을 모두 불러올 때까지 Rendering 을 멈추어서 promotion 같은 페이지들은 pre-rendering 하면 오히려 더 랜더링 시간이 길어지게 되는 현상이 발생하게된다.
그래서 dynamic import 를 사용하여 ssr 설정을 지운뒤 csr 로 불러와 랜더링성능을 향상시켰다.

ex) signin.tsx

const SignInForm = dynamic(
  () => import('../components/SignIn/organisms/SignInForm'),
  { ssr: false, loading: () => <DotoriLogo /> }
);

시행착오

직접접근 방식

처음 url 직접접근방식을 구현할떄 ssr 에서 Authorization 여부에 따라 redirect 하도록 했다

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { Authorization } = await getToken(ctx);
  if (!Authorization) {
    return {
      redirect: {
        destination: '/signin',
        permanent: false,
      },
    }
  }
  return { props: {} };
}

이 방식도 나쁘진 않았는데 각 페이지마다 이런 설정을 해줘야됐고 구지 getServerSideProps 을 안써도 되는 페이지에서도 적용해야됬고 이것때문에 베포환경에서 에러가 떳다. 그래서 더 좋은 방법을 찾아보던중 현재의 Middleware 로 바꾸게됬다.

다크모드 깜빡임

홈페이지에서 다크모드로 설정한후 새로고침하면 흰배경이 됐다가 다시 검은색 배경이 되는 이슈가있었다. 한번 적용을 해봤는데 app.tsx 에는 window, document 를 지원을 안해서 쿠키에 접근을 못했다 그러다 _document 에는 접근할 수 있다는 글을 보고 다크모드 쿠키여부에따라 document 에 theme 를 미리 정해줘서 깜빡임을 막을 수 있었다.

ex) _document.tsx

const themeInitializerScript = `
  (function () {
    document.body.dataset.theme = document.cookie.match('(^|;)?themeCookie=([^;]*)(;|$)')[2] || (window.matchMedia?.('(prefers-color-scheme: dark)').matches ? "dark" : "light");
  })();
`;

export default function Document() {
  return (
    <Html lang="ko">
      <Head>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <body>
        <script dangerouslySetInnerHTML={{ __html: themeInitializerScript }} />
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

글을 마치며

프로젝트를 하면서 실제 학교학생들이 사용중인 프로젝트라 더 열심히 하게됐고 베포하게됐을때 성취감도 더 큰것 같다.
또 여러사용자가 사용하니까 만들당시에는 문제없어보였던게 베포되고나서 많은 이슈와, 피드백이 나와서 다음엔 좀더 사용자입장에서 만들어야겠다고 생각했다.
열심히 이슈찾아주고 피드백해준 학교친구들과 개발할때 더 좋은코드로 짤 수 있게 코멘트 해준 학교선배님들에게 감사의 인사를 전한다. 🙇‍♂️

profile
프론트엔드 공부하고있는 유환빈입니다

0개의 댓글