포트폴리오 사이트 제작기 1탄: React SEO 최적화하기에서 발생한 문제와 그 해결 과정

1. SEO 최적화 코드가 배포시에 동작하지 않았던 문제

SEO 최적화를 위한 코드는 다음과 같았습니다. 

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import prerender from "@prerenderer/rollup-plugin";
import puppeteerRenderer from "@prerenderer/renderer-puppeteer";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");

  return {
    plugins: [
      react(),
      prerender({
        routes: ["/", "/main"],
        renderer: puppeteerRenderer,
        server: {
          port: Number(env.VITE_SERVER_PORT),
          host: env.VITE_SERVER_HOST,
        },
        rendererOptions: {
          maxConcurrentRoutes: 1,
          renderAfterTime: 500,
        },
        postProcess(renderedRoute) {
          renderedRoute.html = renderedRoute.html
            .replace(/http:/i, "https:")
            .replace(
              /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/i,
              env.VITE_BASE_URL || ""
            );
        },
      }),
    ],
  };
});

 

 

이 상태로 main 브랜치에 Git Push를 하여 CI / CD를 통해 배포를 하려고 했으나 안되었기에, EC2에 들어가서 확인해보니 다음과 같은 에러 로그를 확인할 수 있었습니다.

[plugin:Prerender Plugin] [plugin Prerender Plugin] Unable to prerender all routes!
x Build failed in 3.47s
error during build:
[Prerender Plugin] [plugin Prerender Plugin] listen EACCES: permission denied 127.0.0.1:443
    at getRollupError (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/parseAst.js:396:41)
    at error (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/parseAst.js:392:42)
    at Object.error (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/node-entry.js:19589:20)
    at Object.<anonymous> (/home/ec2-user/git/ci-cd-study/fe/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:140:34)
    at Generator.throw (<anonymous>)
    at rejected (/home/ec2-user/git/ci-cd-study/fe/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:29:65)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

 

이러한 로그를 보고 난 뒤에 `Prerender/@Puppeteer`를 만든 레포지토리의 Github Issue에서 찾아봤으나 동일 문제는 없는 걸로 확인했었습니다. 이 문제를 해결하기 위해 공식문서를 보다보니 서버 옵션은 따로 설정해주지 않는 것으로 보아 아래처럼 서버 옵션을 주석처리하고 실행해보았습니다.

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import prerender from "@prerenderer/rollup-plugin";
import puppeteerRenderer from "@prerenderer/renderer-puppeteer";
import puppeteer from "puppeteer";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");

  return {
    plugins: [
      react(),
      prerender({
        routes: ["/", "/main"],
        renderer: puppeteerRenderer,
        // server: {
        //   port: Number(env.VITE_SERVER_PORT),
        //   host: env.VITE_SERVER_HOST,
        // },
        rendererOptions: {
          maxConcurrentRoutes: 1,
          renderAfterTime: 500,
        },
        postProcess(renderedRoute) {
          renderedRoute.html = renderedRoute.html
            .replace(/http:/i, "https:")
            .replace(
              /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/i,
              env.VITE_BASE_URL || ""
            );
        },
      }),
    ],
  };
});

 

 

로컬에서 빌드는 잘되었기 때문에 안심하고 배포했으나 EC2 인스턴스에서는 배포를 실패했습니다.

에러 로그부터 읽어보게 되었습니다.

[ec2-user@ip-172-31-2-9 fe]$ npm run build

> fe@0.0.0 build
> tsc && vite build

vite v5.3.1 building for production...
✓ 45 modules transformed.
[plugin:Prerender Plugin] [plugin Prerender Plugin] Unable to prerender all routes!
x Build failed in 3.54s
error during build:
[Prerender Plugin] [plugin Prerender Plugin] Failed to launch the browser process!
/home/ec2-user/.cache/puppeteer/chrome/linux-126.0.6478.61/chrome-linux64/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory


TROUBLESHOOTING: https://pptr.dev/troubleshooting

    at getRollupError (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/parseAst.js:396:41)
    at error (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/parseAst.js:392:42)
    at Object.error (file:///home/ec2-user/git/ci-cd-study/fe/node_modules/rollup/dist/es/shared/node-entry.js:19589:20)
    at Object.<anonymous> (/home/ec2-user/git/ci-cd-study/fe/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:140:34)
    at Generator.throw (<anonymous>)
    at rejected (/home/ec2-user/git/ci-cd-study/fe/node_modules/@prerenderer/rollup-plugin/dist/RollupPrerenderPlugin.js:29:65)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

 

에러를 천천히 읽어보고, 로그에 있는 트러블 슈팅 관련 문서를 살펴보니, 리눅스(EC2)에 퍼피티어를 실행할 환경이 없어서 그런 것이었습니다.

 

리눅스(EC2)에 퍼피티어 관련 코드를 추가해주고, vite.config에는 다음과 같이 설정을 추가해주었습니다.

 

1. EC2 인스턴스에 puppeteer 관련 코드 추가

sudo yum update -y

sudo yum install -y atk.x86_64 cups-libs.x86_64 gtk3.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 pango.x86_64 alsa-lib.x86_64

 

 

2. vite.config.ts ➡️ puppeteer 관련 코드 실행

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import prerender from "@prerenderer/rollup-plugin";
import puppeteerRenderer from "@prerenderer/renderer-puppeteer";
import puppeteer from "puppeteer";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");

  (async () => {
    const browser = await puppeteer.launch({
      headless: true, // 브라우저가 백그라운드에서 실행되도록 설정
      args: ["--no-sandbox", "--disable-setuid-sandbox"],
    });
    const page = await browser.newPage();
    await page.goto("http://example.com");
    const html = await page.content();
    console.log(html);
    await browser.close();
  })();

  return {
    plugins: [
      react(),
      prerender({
        routes: ["/", "/main"],
        renderer: puppeteerRenderer,
        // server: {
        //   port: Number(env.VITE_SERVER_PORT),
        //   host: env.VITE_SERVER_HOST,
        // },
        rendererOptions: {
          maxConcurrentRoutes: 1,
          renderAfterTime: 500,
        },
        postProcess(renderedRoute) {
          renderedRoute.html = renderedRoute.html
            .replace(/http:/i, "https:")
            .replace(
              /(https:\/\/)?(localhost|127\.0\.0\.1):\d*/i,
              env.VITE_BASE_URL || ""
            );
        },
      }),
    ],
  };
});

 

이럼에도 불구하고 404 에러가 발생해서 보니 CSS, JS 파일에 접근을 못한다는 net::ERR_ABORTED 404(NOT Found) 에러가 발생했었습니다.

 

때문에 EC2에 접속해서 지정된 디렉토리와 그 하위의 모든 파일과 디렉토리가 nginx 사용자와 그룹에 의해 관리되고, 적절한 권한 설정을 가지도록 다음 2 가지 명령어를 입력하였습니다.

sudo chown -R nginx:nginx /home/ec2-user/git/ci-cd-study/be/public/assets
sudo chmod -R 755 /home/ec2-user/git/ci-cd-study/be/public/assets
  • `chown`: Change Owner, 파일이나 디렉토리의 소유자와 그룹을 변경하는 명령어
  • `-$`: Recursive, 지정한 디렉토리 내의 모든 파일과 하위 디렉토리에 대해서도 동일하게 소유자와 그룹을 변경
  • `nginx:nginx`: 변경할 소유자와 그룹을 지정 (소유자 - nginx, 그룹 - nginx )
  • `/home/ec2-user/git/ci-cd-study/be/public/assets`: 소유자와 그룹을 변경할 대상 디렉토리
    • 소유자(Owner): 특정 파일이나 디렉토리를 소유한 사용자. 해당 파일이나 디렉토리에 대한 모든 권한을 가질 수 있으며 다른 사용자에게도 접근 권한 설정 가능
    • 그룹(Group): 파일이나 디렉토리의 접근 권한을 공유하는 사용자의 집합. 파일이나 디렉토리는 하나의 그룹에 속할 수 있으며, 그 그룹의 모든 사용자가 해당 파일이나 디렉토리에 대해 지정된 권한을 가질 수 있음
  • `chmod`: Change Mode, 파일이나 디렉토리의 권한을 변경하는 명령어
  • `755`: 소유자는 읽기, 쓰기, 실행 권한 모두 있음 / 그룹은 읽기, 실행 권한 있음 / 기타 사용자는 읽기 실행 권한 있음

 

이후에도 403 forbidden 에러가 발생했습니다. 

이를 해결하기 위해서는 2가지 설정을 해야 했습니다.

 

먼저 nginx 설정 파일에 정적 파일 서빙하는 경로에 대한 설정을 추가해주었습니다.

sudo vi /etc/nginx/nginx.conf​
location /assets/ {
  alias /home/ec2-user/git/ci-cd-study/be/public/assets/;
  autoindex on;
  allow all;
}

그리고 설정을 적용을 해주는 명령어를 입력하자

sudo systemctl start nginx

 

두번째로 지정된 디렉토리와 그 하위의 모든 파일과 디렉토리가 nginx 사용자와 그룹에 의해 관리되고, 적절한 권한 설정을 가지도록 다음 2 가지 명령어를 추가로 입력해주었습니다.

sudo chown -R ec2-user:ec2-user /home/ec2-user/git/ci-cd-study/be/public/
sudo chmod -R 755 /home/ec2-user/git/ci-cd-study/be/public/

 

 

403 에러 : 작동중인 서버에 클라이언트의 요청이 도달했으나, 서버가 클라이언트의 접근을 거부할 때 반환하는 HTTP 응답 코드이자 오류 코드
404 에러 : 사용자가 사이트에서 존재하지 않는 URL을 탐색했을 때 발생

 

 

2. 빌드시 메모리 초과문제 발생

<--- Last few GCs --->

[8681:0x54d2ab0]    26436 ms: Mark-sweep (reduce) 473.7 (485.9) -> 472.7 (486.2) MB, 833.6 / 0.0 ms  (average mu = 0.123, current mu = 0.009) allocation failure scavenge might not succeed


<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
 1: 0xb09980 node::Abort() [node]
 2: 0xa1c235 node::FatalError(char const*, char const*) [node]
 3: 0xcf77be v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xcf7b37 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xeaf3d5  [node]
 6: 0xebf09d v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 7: 0xec1d9e v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
 8: 0xe832da v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
 9: 0x11fc026 v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
10: 0x15f0a99  [node]
sh: line 1:  8681 Aborted                 vite build

 

해결 방법 1. Node.js의 메모리를 늘리기

1. 현재 용량 확인하기
$ node -e 'console.log(v8.getHeapStatistics().heap_size_limit/(1024*1024))'
$ 499.25

2. 용량 늘리기
NODE_OPTIONS=--max-old-space-size=8000 npm run build

 

방법 1로도 해결되지 않음... EC2 프리티어의 RAM이 부족해서 발생한 문제

 

해결 방법 2. 메모리 스왑

부족한 RAM 메모리을 디스크의 일부를 대신 사용하도록 설정해줌으로써 해결하는 방법이다. 이를 메모리 스왑이라고 함. 

sudo dd if=/dev/zero of=/mnt/swapfile bs=1M count=2048
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile

 

 

현재 배포된 포트폴리오 사이트: https://homebody-coder.com/

'프로젝트들' 카테고리의 다른 글

프로젝트 초기에 할 일  (2) 2024.04.07