過去在做專案的遷移的時候,許多開發者在使用 Nuxt3 時,會延續 Nuxt2 或其他框架的習慣,使用 vite.define
來處理環境變數。這種方式超級不安全的,近期就發現公司其他的專案環境變數的 token 居然就外洩出去了,然後追查中發現居然就是 vite.define 所造成的問題,所以這次我特別寫了這篇文章來存檔,除了提醒自己以後要特別注意外,也讓其他還沒有遇到問題的朋友可以特別注意。
因為主要這件事情網路上去搜尋很多資料都還是教大家使用 vite.define 來搭配 process.env ,但這會造成你在 build 的時候把你的一些隱秘的 token 什麼的打包到 dist 或是 .output 資料夾中,這真的太危險了!
// ❌ 不安全的做法
// nuxt.config.js
export default defineNuxtConfig({
vite: {
define: {
'process.env':process.env
},
}
});
// 在 components/ 或 page/ 中使用
const payToken = process.env.PAY_TOKEN; // 🚨 超危險!
const slackToken = process.env.SLACK_TOKEN; // 🚨 超危險!
// Build 後的 JavaScript 中會直接出現:
const payToken = "app_clpTkttGNzsJYcqwc1232aBFxS4"; // 🚨 直接暴露!
const slackToken = "xoxb-1234567890123-abc2ff13ddefgh"; // 🚨 極度危險!
特點 | vite.define | Nuxt3 runtimeConfig |
安全性 | ❌ 全部暴露 | ✅ 自動區分公開/私密 |
效能 | ❌ Bundle 膨脹 | ✅ 支援 Tree Shaking |
動態替換 | ❌ Build 時固定 | ✅ Runtime 替換 |
TypeScript | ❌ 類型不安全 | ✅ 自動類型推斷 |
SSR / CSR | ❌ 設定上混亂 | ✅ 完美支援 |
使用官方文件的推薦的方式 runtimeConfig 可以避免這類的問題,以及 .env 的環境變數加上 NUXT_PUBLIC_ 或 NUXT_ 前綴字 Nuxt 就可以自動帶入你的 .env 的環境變數。
NUXT_VARIABLE_NAME
=> Private 配置,只在 server-side 可用NUXT_PUBLIC_VARIABLE_NAME
=> Public 配置,client 和 server 都可用// nuxt.config.js
export default defineNuxtConfig({
runtimeConfig: {
// Private 私密(只在 server-side)
payToken: '', // NUXT_PAY_TOKEN
slackToken: '', // NUXT_SLACK_TOKEN
public: {
// Public 公開(client + server)
apiUrl: '', // NUXT_PUBLIC_API_URL
gaId: '', // NUXT_PUBLIC_GA_ID
}
}
});
// .env
NUXT_PUBLIC_API_URL=https://staging.api.com/
NUXT_PUBLIC_GA_ID=G-3BMYF8K234
NUXT_PAY_TOKEN=app_clpTkttGNzsJYcqwc1232aBFxS4
NUXT_SLACK_TOKEN=xoxb-1234567890123-abc2ff13ddefgh
// ❌ 傳統舊專案的方法
<script setup>
const apiUrl = process.env.API_URL;
</script>
// ✅ Nuxt官方所提供的方法
<script setup>
const config = useRuntimeConfig();
const apiUrl = config.public.apiUrl;
// 只在 Client 端可用
if (import.meta.client) {
console.log('Public config:', config.public);
}
</script>
// server/api/payment.js
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event);
// ✅ 可以存取所有的環境變數
const payToken= config.tapPayAppKey; // Private
const apiUrl = config.public.apiUrl; // Public
// 處理後續邏輯...
});
// ❌ 傳統專案的舊方法
if (process.server) {
console.log('Server');
}
if (process.client) {
console.log('Client');
}
// ✅ Vite 以及 Nuxt 的新方法
if (import.meta.server) {
console.log('Server');
}
if (import.meta.client) {
console.log('Client');
}
關於 API 的管理跟架構可以參考我之前寫的這篇文章,使用Axios你的API都怎麼管理?
❌ 舊的專案 API 使用方式
// app/api/apiUtils/apiDomain.js
export const apiDomain = () => {
return process.env.API_URL;
};
// app/api/apiAuth.js
import axios from 'axios';
import { apiDomain } from '@/api/apiUtils/apiDomain.js';
const authRequest = axios.create({
baseURL: process.env.API_URL,
});
接下來你改成 runtimeConfig 就會出現 ❌模組化 API 檔案的 composable 的錯誤問題
// app/api/apiUtils/apiDomain.js
export const apiDomain = () => {
const config = useRuntimeConfig(); // ❌ 錯誤:在模組載入時執行
return config.public.apiUrl;
};
// app/api/apiAuth.js
import { apiDomain } from '@/api/apiUtils/apiDomain.js';
const authRequest = axios.create({
baseURL: apiDomain(), // ❌ 立即執行,沒有 Nuxt context
});
✅ 正確的處理方式
解決方案:Plugin + Composable
// plugins/api-create.js
import axios from 'axios';
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
const request = axios.create({
baseURL: config.public.apiUrl,
});
return {
provide: { request },
};
});
建立一個 Composable
// 建立 composables/useApiCreate.js
export const useApiCreate = () => {
const { $request } = useNuxtApp();
return $request;
};
// app/api/apiAuth.js
import { useApiCreate } from '@/composables/useApiCreate.js';
/*
* 取得用戶的 info 資料 API
*/
export const apiGetUserInf0Auth = data => {
const request = useApiCreate();
return request.get('/api/user/info');
};
Q1: Client 端讀取不到 Private 配置的環境變數怎麼辦?
A: 這是正確的安全使用方式!Private 配置只能在 server-side 使用:
// ✅ 正確:在 server API 中使用
// server/api/auth.js
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event);
const payToken = config.payToken; // 可以存取
});
// ❌ 錯誤:在 Client 端嘗試存取
// pages/index.vue
<script setup>
const config = useRuntimeConfig();
console.log(config.payToken); // undefined (這是安全機制)
</script>
Q2: TypeScript 支援如何配置?
A: Nuxt3 會自動生成類型,但也可以手動定義:
// types/runtime-config.d.ts
declare module 'nuxt/schema' {
interface RuntimeConfig {
payToken: string;
slackToken: string;
}
interface PublicRuntimeConfig {
apiUrl: string;
gaId: string;
}
}
export {}
從 vite.define
遷移到 Nuxt3 runtimeConfig
不僅是一個技術升級,更是一個重要的安全性提升。這個遷移可以:
雖然遷移需要一些工作,但考慮到安全性和長期維護性,這是一個必要且值得的投資。
記住:安全性不是可選的,而是必需的。趕快開始你的環境變數遷移計畫,保護你的敏感資料!