학원에서 수행했던 세 개의 프로젝트를 하나의 도메인에서 subpath로 구분해 서비스를 하려고 했는데 example.com/projectname 이런 방식으로 subpath를 넣으려고 하니 제대로 작동하지 않았습니다. Vanilla JS를 사용한 프로젝트도 있고 React를 사용해서 제작한 서비스도 있어서 해결 방법이 서로 달랐습니다. 원래는 문제를 해결한 다음 바로 글을 쓰려고 했는데 귀찮아서 미루다 보니 지금은 그때만큼 기억이 생생하지 않아서 전에 정리한 것 위주로 간략하게 적어보겠습니다.


1. Vanilla JS + Express

  1차 프로젝트는 템플릿 엔진은 ejs를 사용했고 프론트에서는 Vanilla JS, 백엔드에서는 Express를 사용해서 프로젝트를 제작했습니다.

  • Nginx 설정 수정
    location /<subPathName> {
        proxy_pass http://localhost:<포트 번호>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

 

  먼저 .env 파일을 열어 포트 번호가 겹치지 않게 수정해 준 후에 pm2 라이브러리를 이용해서 프로젝트를 실행했습니다. 그러고 나서 /etc/nginx/sites-enabled 폴더에 있는 사이트 설정 파일을 에디터로 열고 원하는 subpath와 앞에서 설정한 포트번호를 추가했습니다. proxy_pass 부분에서 주소 뒤에 '/'을 붙였다가 링크나 이미지가 제대로 작동을 하지 않았었는데 '/'를 지우니 제대로 작동했습니다.

  • proxy_pass http://localhost:3000;
    → /subPathName/img/logo.png로 접속 시 http://localhost:3000/subPathName/img/logo.png로 전달
  • proxy_pass http://localhost:3000/;
    → /subPathName/img/logo.png로 접속 시 http://localhost:3000/img/logo.png로 전달
  • 프론트 코드 수정 (JavaScript, HTML)
async function getLoginStatus() {
  try {
    const response = await fetch(`/<subPathName>/auth/status`);
    if (!response.ok) {
      throw new Error("데이터를 불러오는 중에 문제가 발생했습니다.");
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

async function goToUserPage() {
  try {
    const logintrue = await getLoginStatus();

    if (logintrue.status === true) {
      window.location.href = `/<subPathName>/userpage?user=${logintrue.data.userId}`;
    } else {
      alert("로그인이 필요한 서비스입니다.");
      window.location.href = `/<subPathName>/login`;
    }
  } catch (error) {
    console.error(error);
    alert("로그인이 필요한 서비스입니다.");
    window.location.href = `/<subPathName>login`;
  }
}

 

  그 후에 JaveScript 코드에 있는 fetch, window.location.href의 경로 등에 subpath를 앞에 추가했습니다.

<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Network</title>

    <link rel="stylesheet" href="/<subPathName>/main.css" />
    <link rel="stylesheet" href="/<subPathName>/layout/header.css" />
    <link rel="stylesheet" href="/<subPathName>/network/network.css" />
    <link rel="icon" href="/<subPathName>/img/favicon.png" type="image/x-icon" />
  </head>
  <body>
    <div class="container">
      <!-- header -->
      <div class="header">
        <div class="logo">
          <a href="/<subPathName>">
            <img class="logo-image" src="/<subPathName>/img/logo.png" alt="logo" />
          </a>
        </div>
    <script src="/<subPathName>/layout/header.js"></script>
    <script src="/<subPathName>/network/network.js"></script>
  </body>
</html>

 

  html 파일 역시 경로 앞에 subpath를 추가했습니다.

  • 백엔드 코드 수정
app.set("views", __dirname + "/views");

app.use("/<subPathName>", express.static(path.join(__dirname, "/views")));
app.use("/<subPathName>", express.static(path.join(".", "uploads/profileImg")));

app.get("/<subPathName>", (req, res) => {
  res.render("network/network.html");
});

app.get("/<subPathName>/login", (req, res) => {
  res.render("login/login.html");
});

app.use("/<subPathName>/auth", authRouter);
app.use("/<subPathName>/users", userRouter);

app.use((req, res, next) => {
  res.redirect("/<subPathName>/404");
});

 

  index.js 파일을 위와 같이 수정했습니다. router 파일의 get, post와 같은 경로에는 subpath를 추가하지 않습니다. 이미 route로 접속할 때 subpath가 추가되도록 설정을 해주었기 때문입니다.


2. React + Express

  2차 프로젝트는 React와 Express를 이용해서 서비스를 제작했습니다. React는 CRA를 이용해서 초기 세팅을 했습니다.

  • Nginx 설정 수정
    location /<subPathName> {
        alias /var/www/project2/client/build;
        index index.html;
        try_files $uri $uri/ /<subPathName>/index.html;
    }

    location /<subPathName>/api {
        proxy_pass http://localhost:<포트 번호>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

 

  프론트 코드의 경우 npm run build를 이용해서 build를 해준 뒤 static 파일로 연결을 해주었고, 백엔드 코드의 경우 pm2로 프로젝트를 실행했습니다. static 경로를 설정할 때 root가 아닌 alias를 사용해야 location에서 설정한 subpath로 접속 시 alias에서 설정해 준 경로로 변환해서 접속을 하게 됩니다.

  • 프론트 코드 수정 (package.json, App.js)
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "/<subPathName>",
  "dependencies": {
  ...
  }
}

 

  먼저 package.json 파일을 열어 homepage 항목을 추가하고 subpath를 설정했습니다.

function App() {
  return (
    <PageWrapper className='App'>
      <Router basename='/<subPathName>'>
        <AppContent />
      </Router>
    </PageWrapper>
  );
}

 

  App.js를 열어 Router basename에 subpath를 추가했습니다. Routh path에는 추가하지 않아도 됩니다.

const OptionContainer = styled.div`
  span {
    font-size: 32px;
    line-height: 60px;
    font-weight: bold;
    margin: 0 15px;
  }
  select {
    margin-left: 15px;
    width: 150px;
    font-size: 25px;
    border-style: none;
    border-bottom: 3px solid #5fc3c8;
    appearance: none;
    background: url('./img/recommendInput/polygon1.png') no-repeat right 10px
      center;
    background-size: 15px;
  }
}

 

  App.js까지 수정해 주었다면 따로 수정해 줄 부분은 없지만 만약 이미지 url 경로가 '../img'로 되어 있다면 부모 디렉터리의 주소가 기준이 되기 때문에 이미지가 제대로 표시되지 않습니다. './img'로 바꿔줘야 현재 디렉터리를 기준으로 해서 이미지가 제대로 표시됩니다.

  • 백엔드 코드 수정 (1차와 동일)
app.use('/<subPathName>',express.static(path.join(__dirname, 'client')));

app.use('/<subPathName>/api/allResearch', allResearchRouter);

 

  1차 프로젝트에서 했던 것과 같이 static, router 경로에 subpath를 추가했습니다.


3. React (Vite) + Express, TypeScript

  3차 프로젝트는 TypeScript, React (Vite), Express를 이용해서 제작했습니다.

  • Nginx 설정 수정

  2차 프로젝트와 다른 점은 build 할 때 npm run build가 아닌 vite build를 사용했다는 것입니다. 다른 설정은 동일합니다.

  • 프론트 코드 수정 (vite.config.ts, App.tsx)
export default defineConfig({
  base: '/<subPathName>',
  plugins: [react(), svgr()],
  resolve: {
    alias: {
      '/img': '/src/assets/img'
    }
  }
});

 

  2차에서는 package.json 파일을 수정했지만 이번에는 vite.config.ts 파일을 수정했습니다. base 항목에 subpath를 추가했습니다.

const App = () => {
  return (
    <Router basename="/<subPathName>">
      <Routes>
        <Route path='/' element={<Home />} />
        <Route path='/search' element={<Search />} />
        <Route path='*' element={<NotFound />} />
      </Routes>
    </Router>
  );
};

 

  2차에서 했던 것과 같이 App.tsx 파일의 Router basename에 subpath를 추가했습니다.

const refreshApi = axios.create({
  baseURL: '/',
  withCredentials: true
});

// 토큰 갱신
const refreshAuthToken = async () => {
  try {
    const response = await refreshApi.post(`/<subPathName>/api/auth/token`);
    return response.data.token;
  } catch (err) {
    console.error('Refresh token failed:', err);
    throw err;
  }
};

 

  만약 fetch나 axios 경로가 있다면 여기에도 subpath를 추가해 줍니다.

  • 백엔드 코드 수정 (1차, 2차와 동일)

  앞에서 했던 것과 동일하게 static, router 경로에 subpath를 추가했습니다.

+ Recent posts