Nuxt3 proxy 설정하기(feat. Spring API 서버)

들어가며

Vue3 프로젝트를 할때는 Vite 의 경우 vite.configWebpack 의 경우 vue.config 파일을 통해 proxy 설정을 할 수 있다. 하지만 현재 2023-08-13 기준 Nuxt3 같은 경우에는 공식문서상 proxy 설정에 대한 설정을 찾아 볼 수 없다. Nuxt3 proxy 에 대해서는 Nuxt issue 에 많은 글들이 달렸는데 하나씩 써보면서 문제점과 결국 어떤것을 써야하는지 알아보자.

 

준비

Nuxt3 v3.6.5 과 Spring 을 이용해 간단히 만든다. 둘다 기본 포트인 Nuxt 3000번 포트, Spring 8080번 포트를 사용한다.

 

proxyTest.vue

<template>
    <div>
        <button @click="() => refreshFullPath()">refreshFullPath</button>
        <div>fullPathData : {{ fullPathData }}</div>
        <button @click="() => refreshRelativePath()">refreshRelativePath</button>
        <div>relativePathData : {{ relativePathData }}</div>
        <NuxtLink to="/">home</NuxtLink>
    </div>
</template>
<script setup lang="ts">
const page = ref(0);
const { data: fullPathData, refresh: refreshFullPath } =
    await useAsyncData<number>(() =>
        $fetch("http://localhost:8080/min/api/test", {
            params: { page: page.value++ },
        }),
    );

const { data: relativePathData, refresh: refreshRelativePath } =
    await useAsyncData<number>(() =>
        $fetch("/min/api/test", {
            params: { page: page.value++ },
        }),
    );
console.log("fullPathData : ", fullPathData.value);
console.log("relativePathData : ", relativePathData.value);
</script>

TestController.java

@RestController
@RequestMapping("/min/api")
@Slf4j
public class TestController {

    @GetMapping("/test/{no}")
    public ResponseEntity<Long> testWithPathVariable(@PathVariable Long no) throws InterruptedException {
        log.info("testWithPathVariable 호출 no : {}", no);
        Thread.sleep(500);
        return ResponseEntity.ok(no);
    }

    @GetMapping("/test")
    public ResponseEntity<Map<String, Object>> testWithParam(@RequestParam Map<String, Object> data) throws InterruptedException {
        log.info("testWithParam 호출 data : {}", data);
        Thread.sleep(500);
        return ResponseEntity.ok(data);
    }

}

해당 위의 코드를 실행하면 문제가 발생한다. 어떤 문제가 발생하는지 알아보자.

상황

SSR

CSR

첫 페이지 요청시 일어나는 SSR 시에는 Nuxt 서버에서 바로 Spring 으로 Request 를 보내기때문에 fullPath 로의 요청은 성공해 응답을 받지만 relativePath 같은 경우에는 기본적으로 Nuxt 서버 자체의 Server 디렉토리를 바라보게 되는데 해당 API 가 존재하지 않으니 null 을 반환하게 된다.

 

router 를 이용해 페이지를 이동하는 경우인 CSR 시에는 fullPath 같은 경우에는 브라우저의 CORS 때문에 요청이 실패하며 relativePath 또한 존재하지 않는 리소스 요청이므로 404 에러가 난다.

(특이하게 CSR시 요청 실패를 두번하는것을 볼 수 있는데 이는 Nuxt 가 사용하고있는 $fetch(ofetch) 의 auto retry 옵션이 GET 요청의 경우 한번 더 실행하기 때문이다.)

 

이 문제는 Spring 의 CORS 설정을 통해 해결할 수 있지만 Nuxt 로는 어떻게 할 수 있는지 알아보자. 

 

Vite

Nuxt3 는 nuxt.config 파일에 여러가지 설정을 넣을 수 있는데 그 중 vite 설정도 넣을 수 있다. 해당 설정을 적용해보자.

설정

// nuxt.config.ts
export default defineNuxtConfig({
    vite: {
        server: {
            proxy: {
                "/api": {
                    target: "http://localhost:8080",
                    changeOrigin: true,
                },
            },
        },
    },
})

결과

SSR

CSR

실행 결과를 보면 SSR 시에는 relativePath 의 요청은 실패해서 결과가 null이다. CSR 시에는 relativePath 는 성공적으로 Spring쪽에 요청한다. 그 이유는 SSR 환경에서는 vite의 proxy 설정을 타지 않고 요청을 보내고 CSR 환경에서는 proxy 설정이 정상적으로 타면서 요청을 보냈기 때문이다.

 

Nitro devProxy

위의 Nuxt issue 를 보다보면 Nuxt 팀에서 일하고있는 개발자가 답변한 내용이 있다. Nuxt3 의 내장서버인 Nitro 의 설정을 통한 proxy 설정을 할 수 있다고 한다. 하지만 잘 안된다는 댓글이 몇개 달려있다. 한번 적용 후 확인해보자.

설정

// nuxt.config.ts
export default defineNuxtConfig({
    nitro: {
        devProxy: {
            "/min/api": {
                target: "http://localhost:8080/min/api",
                changeOrigin: true,
            },
        },
    },
});

결과

SSR

CSR

해당 devProxy 설정또한 Vite 설정과 같은 문제를 가지고 있다.

 

Proxy middleware

Nuxt의 Server Middleware 를 통해 모든 요청을 가로챌 수 있다. 해당 기능을 이용해 특정 요청 url 을 Nuxt 에 기본 내장되어있는 proxyRequest 을 통해 요청을 감싸 사용 할 수 있다. 

설정

// /server/middleware/proxy.ts
export default defineEventHandler(event => {
    if (!event.node.req.url?.startsWith("/min/api")) return;

    const target = new URL(event.node.req.url, "http://localhost:8080");

    return proxyRequest(event, target.toString(), {
        headers: {
            host: target.host,
            origin: target.origin,
        },
    });
});

결과

SSR

CSR

초기 SSR 에서 fullPath, relativePath 두가지 다 성공한 것을 볼 수 있다. 즉, Vite 설정이나 Nitro devProxy 설정과는 다르게 서버사이드에서 proxy 가 제대로 타고 있다는것을 확인 할 수 있다. 또한 CSR 요청도 정상적으로 proxy 되어 요청되는것도 확인 할 수 있다.

 

필자는 Nuxt3.0.0 버전부터 이것을 이용해 프로젝트를 정상적으로 개발 해왔다. 하지만 한가지 방법을 더 소개해보겠다.

 

Nitro rotueRules

2023-02 에 드디어 Nuxt3 에 Built-in proxy 기술이 들어왔다. 해당 기능은 위의 Nuxt issue 가 처음 나온 2021-10, built-in proxy 기능 PR 이 생긴 2022-04 이후 거의 1년만에 생긴것이다. 해당 기능은 Nitro 의 버전이 올라간 Nuxt3.2.0 버전부터 사용할 수 있다.

설정

// nuxt.config.ts
export default defineNuxtConfig({
    nitro: {
        routeRules: {
            "/min/api/**": {
                proxy: "http://localhost:8080/min/api/**",
            },
        },
    },
});

결과

SSR

CSR

Proxy middleware 와 똑같이 성공하는모습을 볼 수 있다. 재미있는 사실은 기능은 Proxy middleware 의 코드와 비슷한 형태로 만들어져있다는것을 구현코드를 통해 확인할 수 있다.

 

결론

정상적인 proxy 방법을 사용하기 위해서는 Proxy middleware 나 Nitro routeRules 를 이용하면 된다. 필자는 Nitro routeRules 가 추가된 Nuxt 3.2.0v 이후에도 여전히 Proxy middleware 방법을 사용하고 있다. 그 이유는 요청을 가로챈 뒤 Logging 이나 요청 조작같은 처리를 넣기 편하기 때문이다. 각자 필요한 상황에 맞춰 사용하면 될것같다.

구현 코드는 여기서 확인 할 수 있다.