React项目从.env读取Google API Client ID无效问题排查
React Google OAuth 401错误:从.env读取Client ID无效的排查方案
问题场景
开发集成Google OAuth API的React应用时,直接在App.js中硬编码Google Client ID一切正常,但将相同字符串放入.env文件并通过process.env读取时,OAuth返回401错误(提示客户端ID无效)。打印CLIENT_ID显示其非undefined、非null,且内容与原Client ID一致。
相关代码
App.js
import { useState, useEffect } from "react"; import useScript from "./useScript"; function App() { const api = useScript("https://apis.google.com/js/api.js"); const client = useScript("https://accounts.google.com/gsi/client"); const [login, setLogin] = useState("Authorize"); /* exported gapiLoaded */ /* exported gisLoaded */ /* exported handleAuthClick */ /* exported handleSignoutClick */ /* global gapi */ /* global google */ const CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID; const API_KEY = process.env.REACT_APP_GOOGLE_API_KEY; const DISCOVERY_DOC = "https://www.googleapis.com/discovery/v1/apis/people/v1/rest"; const SCOPES = "https://www.googleapis.com/auth/contacts.readonly"; let tokenClient; let gapiInited = false; let gisInited = false; const gapiLoaded = () => { gapi.load("client", initializeGapiClient); }; const initializeGapiClient = async () => { await gapi.client.init({ apiKey: API_KEY, discoveryDocs: [DISCOVERY_DOC] }); gapiInited = true; maybeEnableButtons(); }; const gisLoaded = () => { tokenClient = google.accounts.oauth2.initTokenClient({ client_id: CLIENT_ID, scope: SCOPES, callback: "" // defined later }); gisInited = true; maybeEnableButtons(); }; const maybeEnableButtons = () => { if (gapiInited && gisInited) { document.getElementById("authorize_button").style.visibility = "visible"; } }; const handleAuthClick = () => { tokenClient.callback = async (resp) => { if (resp.error !== undefined) { throw resp; } if (document.getElementById("signout_button") !== null) { setLogin("Refresh"); } }; if (gapi.client.getToken() === null) { tokenClient.requestAccessToken({ prompt: "consent" }); } else { tokenClient.requestAccessToken({ prompt: "" }); } }; const handleSignoutClick = () => { const token = gapi.client.getToken(); if (token !== null) { google.accounts.oauth2.revoke(token.access_token); gapi.client.setToken(""); setLogin("Authorize"); } }; useEffect(() => { if (login === "Authorize") { document.getElementById("signout_button").classList.add("hidden"); } else { document.getElementById("signout_button").classList.remove("hidden"); } }, [login]); return ( <> {api === "ready" ? gapiLoaded() : api} {client === "ready" ? gisLoaded() : client} <div className="bg-gray-100 w-full min-h-screen h-full px-2 py-10"> <div className="bg-white max-w-2xl mx-auto p-4"> <button id="authorize_button" onClick={handleAuthClick} className="p-2 text-white font-semibold rounded bg-cyan-600 hover:bg-cyan-700" > {login} </button> <button id="signout_button" onClick={handleSignoutClick} className="p-2 text-white font-semibold rounded bg-gray-400 hover:bg-gray-500 ml-2" > Sign Out </button> </div> </div> </> ); } export default App;
.env
REACT_APP_GOOGLE_CLIENT_ID = 'xxxxxxxxxxxxxxxx', REACT_APP_GOOGLE_API_KEY = 'xxxxxxxxxxxxxxxx',
useScript.js
import { useState, useEffect } from "react"; function useScript(src) { const [status, setStatus] = useState(src ? "loading" : "idle"); useEffect( () => { if (!src) { setStatus("idle"); return; } let script = document.querySelector(`script[src="${src}"]`); if (!script) { script = document.createElement("script"); script.src = src; script.async = true; script.setAttribute("data-status", "loading"); document.body.appendChild(script); const setAttributeFromEvent = (event) => { script.setAttribute( "data-status", event.type === "load" ? "ready" : "error" ); }; script.addEventListener("load", setAttributeFromEvent); script.addEventListener("error", setAttributeFromEvent); } else { setStatus(script.getAttribute("data-status")); } const setStateFromEvent = (event) => { setStatus(event.type === "load" ? "ready" : "error"); }; script.addEventListener("load", setStateFromEvent); script.addEventListener("error", setStateFromEvent); return () => { if (script) { script.removeEventListener("load", setStateFromEvent); script.removeEventListener("error", setStateFromEvent); } }; }, [src] // Only re-run effect if script src changes ); return status; } export default useScript;
排查与解决方法
1. 修复.env文件格式错误
你的.env文件存在两个关键问题:
- 等号前后添加了空格,这些空格会被包含到变量值中,导致Client ID实际带有前后空格(肉眼可能难以察觉,但Google OAuth会判定为无效ID)
- 变量值添加了多余的单引号和结尾逗号,React环境变量不需要引号包裹,逗号也会被当作值的一部分
修正后的.env格式:
REACT_APP_GOOGLE_CLIENT_ID=xxxxxxxxxxxxxxxx REACT_APP_GOOGLE_API_KEY=xxxxxxxxxxxxxxxx
2. 重启React开发服务器
修改.env文件后,必须重启本地开发服务器,否则新的环境变量不会生效,应用会继续使用旧的错误值。
3. 验证变量真实值
在App.js中添加以下代码,打印变量的JSON格式,确认没有多余字符:
console.log("CLIENT_ID:", JSON.stringify(CLIENT_ID));
如果输出带有引号或空格(比如" 'xxxxxxxxxxxxxxxx', "),说明格式仍有问题。
4. 确认文件位置与变量名
- .env文件必须放在项目根目录(与package.json同级),不能放在src文件夹内
- 确保变量名完全一致:
REACT_APP_GOOGLE_CLIENT_ID(大小写敏感)
内容的提问来源于stack exchange,提问作者Little_bugfixer
相关产品推荐
相关产品推荐

