같은글 노션링크 | 코드 변경 전후를 명확하게 보실수있습니다.
1.1 성능이 중요한 이유
1.2 최적화 목표
2.1 효율적인 캐시 정책을 사용하여 정적인 애셋 제공하기
2.2 사용하지 않는 자바스크립트 줄이기
2.2.1  종속성 정리
2.2.2 css 축소
2.2.3 폰트 최적화
2.2.4 이미지 최적화
2.2.5 코드 검토
3.1 타이틀 태그
3.2 파비콘 생성하기
3.3 모바일 홈화면 아이콘 생성하기
3.4 오픈그래프 메타태그
3.5 robots.txt 작성, sitemap.xml 생성
3.6 검색엔진에 사이트 등록 
4.1 시맨틱태그 사용하기
4.2 aria-label 속성 추가
4.3 모달창 포커스 되게 만들기
5.1 X-Content-Type-Options
5.2 X-Content-Type-Options
5.3 X-XSS-Protection
5.4 Content-Security-Policy
프론트엔드에서 성능은 웹사이트를 빠르게 보여주는 것이라고 할 수 있다. 단, 속도를 빠르게 하는 것만이 성능 최적화의 전부는 아니다.
웹사이트 성능이 안 좋으면 유저가 떠나고 재방문을 안한다. 매출은 하락한다. 성능이 좋으면 유저가 오래 머물고, 구매도 하고 이벤트도 참여하는 등 서비스 이용률이 상승해 매출이 증가한다.
또한, 구글은 웹 성능은 검색엔진 상위에 노출되는 순위에도 영향을 미친다고 발표했다(2021.05). 검색서비스를 제공하는 이들이므로 높은 성능 지표를 평가받은 사이트를 상위에 노출하는게 당연하다. 즉, 성능은 SEO에도 영향을 미친다.
최적화 작업을 통해 PageSpeed Insight, lightHouse 등의 점수를 올리자!

진단 파악: 정적자산에 대해 캐시 헤더를 설정하라는 것으로 이해했다.
Cache-Control: s-maxage=31536000, immutable 이 적용되고 있다. 즉, 불필요한 조건부확인 네트워크 요청을 하지 않고 유효기간 내의 캐시는 바로 브라우저에서 가져다 쓰고있는 상황이다.  따로 건드릴 필요가 없다고 판단했다.
위 진단을 보면 사용하지 않는 자바스크립트가 모두 node_modules 안의 파일들이다. 쟤네를 삭제해도 된다면 삭제하고 싶은데, 종속성을 직접 삭제하는 건.. 위험+비효율적이라고 판단되어 사용하지않는 JS를 줄일 수 있는 다른 방식들을 찾아봤다.
코드 분할: 하나의 번들 파일을 여러 개의 번들 파일로 나눈 뒤 실제 로드될 화면에 필요한 번들 파일만 불러오고 나머지 번들 파일은 호출하지 않고 지연시킴으로 써 작업량을 줄여 더 빠른 속도로 화면이 보일 수 있게 도와줍니다.
→ webpack, Rollup 같은 빌드 도구를 사용시 해볼만한 시도인듯하다.
트리쉐이킹: js 번들에서 사용되지 않는 코드를 제거하는 기술이다. babel 을 사용하는 경우 트리쉐이킹을 활성화해 데드 코드를 제거했는지 확인할 수 있다고 한다.
→ webpack, Rollup 같은 빌드 도구를 사용시 해볼만한 시도인듯하다.
JS 번들 분석: Webpack Bundle Analyser 또는 소스 맵 탐색기와 같은 도구를 사용하여 JavaScript 번들을 시각화하고 분석하라. 코드의 어느 부분이 사용되지 않는 JavaScript에 영향을 미치는지 식별하는 데 도움이 된다
종속성 정리(Prune Dependencies): node_modules 파일에 사용되지 않은 JavaScript가 포함되어 있는 것을 발견한 경우 프로젝트의 종속성을 검토해야 한다. 불필요한 패키지를 제거하거나 교체하여 번들 크기를 줄일 수 있다.
Lazy Loading: 페이지가 로드될 때 즉시 필요하지 않은 구성 요소나 애플리케이션 부분에 대해 지연 로딩을 구현합니다. 이렇게 하면 처음에 로드되는 JavaScript의 양을 줄이는 데 도움이 될 수 있습니다 → 이미 적용함
이미지 최적화: 종종 이미지가 JavaScript 번들의 크기에 영향을 줄 수 있다고 한다.
코드 검토: JavaScript 코드를 검토하여 사용하지 않는 함수, 변수 또는 전체 파일을 식별하고 제거합니다. 코드베이스를 정리하면 사용하지 않는 JavaScript를 크게 줄일 수 있습니다.
이중 본 프로젝트에서 시도해볼만한 몇가지를 시도했다.
2.2.1 종속성 정리

2.2.2 css 축소 - 안쓰는 css 제거
사용하지 않는 colors를 제거했다.
2.2.3 폰트 최적화 - subset 폰트 적용
[subset 폰트를 생성](https://transfonter.org/)해 다운로드 받는 폰트 용량 크기를 줄였다.
용량 축소 전

용량 축소 후

/* src/style/reset.ts */
@font-face {
    font-family: Galmuri11;
    src:
      local('Galmuri11 Regular'),
      url('fonts/subset-Galmuri11-Regular.woff2') format('woff2'),
      url('fonts/subset-Galmuri11-Regular.woff') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
  }
  @font-face {
    font-family: Galmuri11;
    src:
      local('Galmuri11 Bold'),
      url('fonts/subset-Galmuri11-Bold.woff2') format('woff2'),
      url('fonts/subset-Galmuri11-Bold.woff') format('woff');
    font-weight: bold;
    font-style: normal;
    font-display: swap;
  }
2.2.4 이미지 최적화 - logoxx.png 용량 축소
logo192, logo512, ogImage 이미지 용량 줄이기2.2.5 코드 검토

데스크탑은 성능점수가 96점이지만, 모바일은 69점이 나왔다.(2023.10.23. 11:36:02)

FCP: 페이지로드시점부터 화면에 처음으로 컨텐츠가 렌더링될 때까지의 시간
LCP: 페이지 로드가 시작된 시점부터, 뷰포트 내에서 가장 큰 콘텐츠(텍스트, 이미지, 비디오...)가 렌더링된 시점까지 소요된 시간. 실질적인 로딩 완료시점을 나타내는 핵심 메트릭!
기본적으로 TTFB, FCP가 양호해야 LCP도 우수할 수 있다고 한다. FCP를 개선한 뒤 재측정된 LCP수치를 확인해보자
Speed Index: 웹 페이지가 얼마나 빨리 컨텐츠를 채우는지를 측정하는 지표
앞의 지표를 개선한다면 해당 지표도 개선되리라..!
근데 TBT가 늘어났다. 왜일까..?
TBT는FCP시점부터TTI(Time To Interactive)시점까지의 시간을 말한다. 즉, 컨텐츠는 눈에 보이고 있는데, 인터렉션할 수 있기까지 좀 걸린다는 것이다.
1) css, font 등 최적화를 수행하면서 컨텐츠가 렌더되는 시간(FCP)은 짧아짐
2) GameResult 컴포넌트의 지연로딩을 해제하면서, bundle JS 크기가 커지고, js 다운받는 시간 증가
→ 실제 인터렉션이 가능해지기 까지 걸리는 시간 증가
개선방향은 차차 고민해보자~
index.html 의 head 태그에 추가
    <title>Random Movie Game</title>
#생성하는 방법
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> 로 인해 파비콘이 적용된다.#브라우저 파비콘
(10.19 목)현재 배포사이트에는 적용이 안되고 있다. 아이폰은 바로 적용 됐는데도 말이다.
스택오버플로우에서 파비콘이 업데이트 되는데에 수일이 걸릴 수 있다는 답변을 보았다. 이번주 동안 지켜보고 안바뀌면 조치를 취할 것이다
Simply adding it to the root folder works after a fashion, but I've found that if I need to change the favicon, it can take days to update... even a cache refresh doesn't do the trick.
로컬에 적용된 모습

(10.20 금) 배포사이트에도 적용됐다

#모바일 파비콘

#생성하는 방법
피그마에서 192*192 사이즈의 이미지 파일을 만들고, 파일이름을 logo192.png 로 저장한다.
프로젝트 퍼블릭 폴더에 옮긴다.
menifest.json* 을 다음과 같이 작성한다.
*웹 애플리케이션의 정보를 JSON 텍스트파일로 제공해서 웹 앱의 다운로드를 네이티브 앱과 유사한 형태로 제공할 수 있게 도와주는 파일
index.html의 head 태그 자식으로 다음 태그를 추가한다.
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
apple-touch-icon 이라는 링크를 추가하여 홈화면에 보여질 아이콘을 지정할 수 있다.
// manifest.json 
{
  "name": "Random Movie Game", // icon에 표시될 이름, 보통 설치 배너에 표시되며, 검색 키워드로도 쓰임
  "short_name": "Random Movie", // 공간이 부족한 경우 이름을 짧게 표시하기 위해서 만들어주는 옵션
  "icons": [ 
    {
      "src": "favicon.ico", // 이미지의 경로
      "sizes": "48x48", // 이미지 크기
      "type": "image/x-icon" // 이미지 파일 유형
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".", // 유저가 해당 애플리케이션을 실행하면 이동하는 URL 주소, 상대주소와 절대주소 모두 사용이 가능
  "display": "standalone", // 사용자가 어떻게 애플리케이션을 볼 수 있을 지에 대한 내용을 설정할 수 있는 옵션
  "theme_color": "#ffffff", // 애플리케이션을 켰을 때의 주변부(URL 입력바나 시스템 바)의 색깔을 지정할 수 있는 옵션
  "background_color": "#ffffff" 
}
// index.html
<html lang="ko_KR">
  <head>
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> // 파비콘
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> // 홈화면아이콘
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> // mainfest 연결
  </head>
</html>
#생성 결과
아이콘 적용 비포(빨강)와 애프터(노랑)

#궁금증
// 모바일 웹 앱 개발자는 head 섹션에 아래의 4가지 태그를 추가할 수 있다.
< link rel="apple-touch-icon" href="/apple-touch-icon.png"/>
< link rel="apple-touch-startup-image" href="/startup.png">
< meta name="apple-mobile-web-app-capable" content="yes" />
< meta name="apple-mobile-web-app-status-bar-style" content="black" />apple-touch-icon
등록되는 웹 사이트의 아이콘을 지정할 수 있다.
일반적으로 아이폰에서 웹사이트 아이콘을 추가하게 되면 웹사이트 화면을 캡쳐한 내용을 아이콘으로 사용하는데 apple-touch-icon 이라는 링크를 추가하여 아이콘을 내가 지정한 것으로 사용할 수 있다. favicon 의 아이폰 버전이라고 생각하면 된다.
이 아이콘은 기본적으로 아이폰이 제공하는 UI 처리 ( 모서리를 둥글게 하고 반원형의 밝은 부분을 추가해 주는 것) 가 된다. 원하지 않을 때는 rel 속성의 값을 apple-touch-icon-precomposed 라는 이름으로 지정하여 사용하면 된다.
apple-touch-startup-image
화면이 로딩될 때 스타트업 이미지를 지정할 수 있다. Web App 이지만 앱 처음 로딩시 로고화면 같은걸 보여줄 수 있다. 아이폰 기본 앱에 들어있는 Default.png 와 비슷한 역할이다.
단, 이미지의 크기가 정확히 맞아야 한다. 아이폰은 320×460 , 아이폰4는 640×920 , 아이패드는 768×1004 로 정확히 맞춰야만 제대로 화면에 표시된다.
apple-mobile-web-app-capable
Web App으로 선언하여 브라우저의 UI ( URL 바 ) 를 안 보이도록 할 수 있다.
즉, Web App 이 마치 일반 Native App 처럼 화면 전체 ( 최상단 상태바 20px 제외) 를 활용할 수 있도록 한다.
apple-mobile-web-app-status-bar-style
상태바의 색상을 지정할수 있다. 바탕화면이 검정색인 어플리케이션의 경우 상태바만 회색인 이질감을 줄이기 위해 사용한다.
3가지 스타일 : default (회색) , black , black-translucent ( 반투명 )
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />%PUBLIC_URL%를 사용하여 애플리케이션의 공개 URL을 참조합니다.%PUBLIC_URL%는 애플리케이션의 실제 기본 URL로 대체되어 파비콘과 같은 자산에 대한 올바른 경로가 개발 및 프로덕션 환경 모두에서 사용되도록 합니다.#index.html에 태그 추가하기
<html lang="ko_KR">
  <head>
    <meta
      name="description"
      content="3초 랜덤게임으로 볼만한 영화를 추천받을 수 있습니다. 국가, 개봉연도, 다양성영화 3가지 요소로 박스오피스에 오른 영화를 추천합니다"
    />
    <!-- Open Graph -->
    <meta property="og:title" content="Random Movie Game" />
    <meta property="og:url" content="https://random-game-sigma.vercel.app" />
    <meta property="og:type" content="website" />
    <meta property="og:site_name" content="볼만한 영화 3초만에 추천받기" />
    <meta property="og:locale" content="ko_KR" />
    <meta property="og:locale:alternate" content="en_US" />
    <meta property="og:image" content="%PUBLIC_URL%/ogImage.png" />
    <meta property="og:image:width" content="1200" />
    <meta property="og:image:height" content="630" />
    <meta
      property="og:image:alt"
      content="777 game slot that randomly recommends movies with 3 buttons"
    />
    <meta
      property="og:description"
      content="3초 랜덤게임으로 볼만한 영화를 추천받을 수 있습니다. 국가, 개봉연도, 다양성영화 3가지 요소로 박스오피스에 오른 영화를 추천합니다"
    />
    <!-- twitter -->
    <meta name="twitter:url" content="https://random-game-sigma.vercel.app" />
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:title" content="Random Movie Game" />
    <meta
      name="twitter:image"
      content="https://random-game-sigma.vercel.app/ogImage.png"
    />
    <meta
      name="twitter:description"
      content="3초 랜덤게임으로 볼만한 영화를 추천받을 수 있습니다. 국가, 개봉연도, 다양성영화 3가지 요소로 박스오피스에 오른 영화를 추천합니다"
    />
  </head>
</html>
#적용결과

# robots.txt
# https://www.robotstxt.org/robotstxt.html
# 다른 검색엔진의 로봇에 대하여 수집을 허용하지 않고 네이버, 구글 검색로봇만 수집 허용으로 설정합니다.
User-agent: *
Disallow: /
User-agent: Yeti
User-agent: Googlebot
Allow: /
Sitemap: https://random-game-sigma.vercel.app/sitemap.xml
# sitemap.xml 
<?xml version="1.0" encoding="UTF-8"?>
<urlset
      xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
            http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
  <loc>https://random-game-sigma.vercel.app/</loc>
  <lastmod>2023-10-19T11:10:20+00:00</lastmod>
</url>
</urlset>
아래는 소유권 확인을 위한 태그로서, head 태그 안에 추가하면 된다 .
<meta name="google-site-verification" content="어쩌고 저쩌고 어쩌고 저쩌고" />
<meta name="naver-site-verification" content="어쩌고 저쩌고 어쩌고 저쩌고" />

<Button	onClick={startSpinning} css={startButton} aira-label="게임시작">
	START
</Button>```tsx
<Button css={slot.button} onClick={handleButtonClick} aria-label={btnAriaLabel} />
// props으로 내려받는다.
```
wave - webaim 에서 발견한 항목<Button onClick={onClose} css={alert.button} aria-label={btnText}>
	{btnText}
</Button>1) 이슈: 모달영역은 포커스 되지않고 다시뽑기 버튼만 포커스 되었다.
2) 해결:
role=”dialog”추가해 대화상자하는 역할임을 알려준다tabIndex=0 지정해서 포커스 못받는 태그들이 포커스를 받도록 처리한다.tabIndex: 요소의 tab순서를 지정하는 속성tabIndex = 0 지정시 focus을 받을 수 없는 h1, div 등과 같은 요소들도 tab으로 focus을 받을 수 있도록 처리됨tabIndex = -1지정시 기본적으로 focus을 받는 태그가 tab을 못받도록 처리// GameResult.tsx // 게임결과창 모달
return (
      <BackDrop whiteBoard>
        <section css={gameResult.box} role="dialog" tabIndex={0}> // 포커스 설정
          <Text typography="h3" className="result">
            뽑기결과
          </Text>
          <Text typography="p" css={gameResult.movieNm}>
            {selectedMovie}
          </Text>
	          ... 
            <Button css={gameResult.initButton} onClick={initGame}>
              다시뽑기
            </Button>
          </div>
        </section>
      </BackDrop>
    );
}
3) 결과: 다음과 같이 탭을 눌렀을때 게임 결과창이 포커스가 되어 음성이 나온다. 하지만 이 방법에는 한계가 있다. 나는 모달 영역 전체에 포커스가 가도록 처리했다. 즉, 유저가 모달 내부의 작은 요소를 자유롭게 탭으로 이동하지는 못한다. 탭으로 모달 내부를 순환하는 로직을 꼭 공부해서 적용하자!

5.1~5.3 은 체크봇이라는 도구의 평가결과를 참고해 보안헤더를 적용했다.
받은 진단: Use content sniffing protection
'X-Content-Type-Options: nosniff 헤더 사용하기text/javascript" 또는 "application/javascript")을 가져야만 JavaScript로 해석해야 한다는 의미이다. 이 방어법은 브라우저 의존적이다.받은 진단: Use clickjack protection
<frame>, <iframe>, <object> 등의 태그를 내 웹 앱의 html에 삽입하여 투명한 오버레이 폼을 만들어 유저가 정보를 입력하면 공격자 서버에 전송되도록 만들수 있다. 피해자의 입장에서는 링크를 눌렀을 때 의도했던 것과는 다른 동작을 하게 한다하여 이를 클릭재킹(Clickjacking)이라 부른다.DENY: "이 홈페이지는 다른 홈페이지에서 표시할 수 없음"SAMEORIGIN: "이 홈페이지는 동일한 도메인의 페이지 내에서만 표시할 수 있음"ALLOW-FROM *origin*: "이 홈페이지는 origin 도메인의 페이지에서 표함하는 것을 허용함"받은진단: Use XSS protection (Cross-Site Scripting)
| X-XSS-Protection : 0 | 브라우저의 XSS-Filter 비활성화 | 
|---|---|
| X-XSS-Protection : 1 | 브라우저의 XSS-Filter 활성화 | 
| X-XSS-Protection : 1, mode=block | 브라우저의 XSS-Filter 비활성화 | 
| 전체 페이지의 응답 내용을 # 문자로만 보여줌 | 
#보안헤더 설정하기
프로젝트 루트에 vercel.json 파일을 생성하고 다음 로직을 작성하고 배포한다.
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        }
      ]
    }
  ]
}
#적용결과
#체크봇 진단결과

라이트하우스 분석결과, CSP(content security policy)헤더로 브라우저에 대한 XSS 공격 및 데이터 삽입 공격을 포함한 특정 공격을 방어할 수 있다고 한다.

브라우저는 기본적으로 페이지에서 요청하는 모든 코드를 다운로드하여 실행한다. CSP를 설정함으로서 브라우저에게 어떠한 조건의 리소스만 다운로드 or 실행 or 렌더링하라고 지시를 내릴 수 있다고 한다.
#CSP 작성하기
"default-src 'self'; style-src 'unsafe-inline'; connect-src 'self' https://www.kobis.or.kr;"
```
- default-src 'self': 모든 콘텐츠가 사이트 자체의 출처(하위 도메인 제외)에서 오기를 원합니다.
- style-src 'unsafe-inline'  :인라인 스타일 사용
- connect-src 'self' https://www.kobis.or.kr;" : 연결할 수 url을 제한(ajax, websockets등)
```json
// vercel.json (프로젝트 루트)
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
       ..
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; style-src 'unsafe-inline'; connect-src 'self' https://www.kobis.or.kr;"
        }
      ]
    }
  ]
}
#결과

pageSpeed Insights  성능항목(2023.10.23. 11:36:02)
모바일(2023.10.19)

데스크탑(2023.10.19)

체크봇

pageSpeed Insights 성능항목(2023.11.27)

모바일(2023.11.03)

데스크탑(2023.11.03)

체크봇

lighthouse
모바일

데스크탑

성능 최적화를 시도하려고 보니 웹 앱에 대해 심화학습을 하는 기분이 들었다.
웹 앱이라는 큰 그림이 어떤 세부사항들로 구성돼 있는지 요목조목 알아보는 기분?
기존에 알고만 있었던 지식을 프로젝트 성능 개선을 위해 마주하게 되면서, 좀 더 실용적인 의미로 이해하게 되어 기뻤다.
학습
성능
웹접근성
보안
지리네요