SpringBoot と Nuxt3 を共存させる
まえがき
既存の SpringBoot の View を Nuxt3 に移植したくて、でも一気にはできないので、地道にやっていくために、SpringBoot 側の画面を残しつつ、Nuxt3 も表示できるようにしたときの設定メモです。
SpringBoot 側にルートコンテキストをつけることも考えてたのですが、影響範囲が大きいので、SpringBoot 側の URL は変えずに、Nuxt 側でなんとかする方法を取っています。
環境
Java 17
SpringBoot 3.2
Tomcat 10系
Nuxt 3
Node.js v20
SpringBoot 側
認証
認証(ログイン)は SpringBoot 側で行い、Nuxt は SpringBoot が発行した cookie を使用して、認証を突破します。セキュリティとかはクローズドな環境なのでそっとしておきます。
なお、本番時は同一ホストになるはずなので、cookie は Nuxt がちゃんと飛ばせるはずです。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 認可の設定
http.authorizeHttpRequests(requests -> requests
// セキュリティ設定を無視するリクエスト設定
// 静的リソース(images、css、javascript)に対するアクセスはセキュリティ設定を無視する
.requestMatchers("/img/**", "/css/**", "/javascript/**", "/bower_components/**",
"/dist/**", "/assets/**", "/webjars/**")
.permitAll().requestMatchers("/", "/index").permitAll() // indexは全ユーザーアクセス許可
.requestMatchers("/cook/choco/**").permitAll() // Nuxtの画面へのアクセスは認証不要
.anyRequest().authenticated()); // それ以外は全て認証無しの場合アクセス不許可
// SPAの画面からの認証
http.exceptionHandling(
exceptionHandling -> exceptionHandling.defaultAuthenticationEntryPointFor(
getRestAuthenticationEntryPoint(), new AntPathRequestMatcher("/api/**")));
// 省略
ローカル開発用 Nuxt へ forward させるコントローラ
ちょっとややこしいですが、ローカル開発の場合は localhost:3000 で Nuxt アプリは起動させ、本番環境の場合は同一ホストになるはずなので、Nuxt へforwardさせる方法が変わります。
もっといい方法がある気もしますが、一旦はこれでできているので、目をつぶります。😑
@Controller
@Profile("local")
public class LocalCookController {
@RequestMapping("/cook/choco/**")
public RedirectView redirectToNuxt() {
return new RedirectView("http://localhost:3000/nuxt/cook/choco/");
}
}
本番用 Nuxt へ forward させるコントローラ
@Slf4j
@Controller
@Profile("!local")
public class ProductionCookController {
@RequestMapping("/cook/choco/**")
public String forwardToNuxt() {
log.info("Forwarding to /nuxt/index.html");
return "forward:/nuxt/index.html";
}
}
Nuxt アプリを war ファイルに同梱する
Nuxt アプリをビルドして、webapp フォルダにコピーすることにより、tomcat が静的ファイルとして認識するようになります。
本当は asset 周りも SpringBoot と共有させたかったのですが、古の遺産により断念。
task buildFrontend(type: Exec) {
workingDir 'frontend'
commandLine 'npm', 'install'
commandLine 'nuxi', 'generate'
}
task cleanFrontend(type: Delete) {
delete 'src/main/webapp/nuxt'
}
task copyFrontend(type: Copy) {
from 'frontend/dist/'
into 'src/main/webapp/nuxt'
dependsOn cleanFrontend, buildFrontend
}
processResources.dependsOn copyFrontend
Nuxt3 側の設定
tomcatに静的サイトとして認識させるため、SSR でビルドします。
Nuxt側のルートパスは 『/nuxt/』として、ローカル開発時は『/api』 のパスを SpringBoot 側に流します。
export default defineNuxtConfig({
ssr: false,
app: {
baseURL: '/nuxt/',
// 省略
},
// 省略
vite: {
server: {
proxy: {
'/api': {
target: import.meta.env.VITE_BASE_API_URL,
changeOrigin: true,
secure: false,
},
},
},
build: {
sourcemap: false
},
clearScreen: true,
logLevel: 'info'
},
バックエンドに横流しする関数
NuxtLink などでパスを指定すると、このままだと『/nuxt』 が自動的に付与されます。
しかし、SpringBoot の View に戻したいときはこのままだと到達しないので、『forward: 'backend'』がある場合は、無理やり『/nuxt』を削除するようにする関数を作成しました。
items: [
{ label: '新規登録', to: '/cock/new', forward: 'backend' },
// 省略
<NuxtLink v-if="item.to" :to="getLink(item)"
export const apiUrl = import.meta.env.VITE_BASE_API_URL || '<本番のドメイン>';
export const getLink = (item: { forward: string; to: string; }) => {
if (item.forward === 'backend') {
const path = item.to.replace('/nuxt', '');
return `${apiUrl}${path}`;
} else {
return item.to;
}
};
まあ、テンプレートを使っていたのでこうしたんですが、そうじゃなかったら別のやり方があったかも。。
ローカル開発時の SpringBoot のドメインは .env.develop を作成して、自動的に切り替わるようにしています。
VITE_BASE_API_URL=http://localhost:8080
おわりに
Vue/Nuxt 素人頑張りました。。
仕組みはなんとなくはわかってきたので、いい勉強になったなーと思います。
もうやることはないとは思いますが。
Discussion