Zmienne Środowiskowe
Create T3 App korzysta z paczki Zod↗ w celu walidacji twoich zmiennych środowiskowych podczas runtime’u oraz budowania aplikacji. Dołączane są z tego powodu dodatkowe narzędzia w pliku src/env.js
.
env.js
TLDR; Jeżeli chcesz dodać nową zmienną środowiskową, musisz dodać ją zarówno do pliku .env
, jak i zdefiniować jej walidator w pliku src/env.js
.
Plik ten podzielony jest na dwie części - schemat zmiennych i wykorzystywanie obiektu process.env
, jak i logika walidacji. Logika ta nie powinna być zmieniana.
const server = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
});
const client = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string(),
});
const processEnv = {
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
};
Schemat Dla Serwera
Zdefiniuj tutaj zmienne środowiskowe dla serwera.
Koniecznie nie prefixuj tutejszych kluczy NEXT_PUBLIC_
, aby przypadkiem nie ujawnić ich do klienta.
Schemat Dla Klienta
Zdefiniuj tutaj zmienne środowiskowe dla klienta.
Aby ujawnić zmienne dla klienta dodaj prefix NEXT_PUBLIC
. Jeżeli tego nie zrobisz, walidacja nie zadziała, pomagając ci w wykryciu niewłaściwej konfiguracji.
Obiekt processEnv
Wykorzystaj destrukturyzację obiektu process.env
.
Potrzebny jest nam obiekt, który parse’ować możemy z naszymi schematami Zoda, a z powodu sposobu w jaki Next.js przetwarza zmienne środowiskowe, nie możesz destrukturyzować obiektu process.env
tak jak zwykłego obiektu - trzeba to zrobić manualnie.
TypeScript zapewni poprawność destrukturyzacji obiektu i zapobiegnie sytuacji, w której zapomnisz o jakimś kluczu.
// ❌ To nie zadziała, musimy ręcznie "rozbić" `process.env`
const schema = z.object({
NEXT_PUBLIC_WS_KEY: z.string(),
});
const validated = schema.parse(process.env);
Logika Walidacji
Dla zainteresowanego czytelnika:
Zaawansowane: Logika walidacji
W zależności od środowiska (serwer lub klient) walidujemy albo oba schematy, albo tylko schemat klienta. Oznacza to, iż nawet jeśli zmienne środowiskowe serwera nie będą zdefiniowane, nie zostanie wyrzucony błąd walidacji - możemy więc mieć jeden punkt odniesienia do naszych zmiennych.
const isServer = typeof window === "undefined";
const merged = server.merge(client);
const parsed = isServer
? merged.safeParse(processEnv) // <-- na serwerze, sprawdź oba schematy
: client.safeParse(processEnv); // <-- na kliencie, sprawdź tylko zmienne klienta
if (parsed.success === false) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(parsed.error.format()),
);
throw new Error("Invalid environment variables");
}
Następnie korzystamy z obiektu proxy, aby wyrzucać błędy, jeśli chcesz skorzystać z serwerowych zmiennych środowiskowych na kliencie.
// proxy pozwala na zmianę gettera
export const env = new Proxy(parsed.data, {
get(target, prop) {
if (typeof prop !== "string") return undefined;
// na kliencie pozwalamy jedynie na zmienne NEXT_PUBLIC_
if (!isServer && !prop.startsWith("NEXT_PUBLIC_"))
throw new Error(
"❌ Attempted to access serverside environment variable on the client",
);
return target[prop]; // <-- w przeciwnym razie, zwróć wartość
},
});
Korzystanie Ze Zmiennych Środowiskowych
Jeżeli chcesz skorzystać ze swoich zmiennych środowiskowych, możesz zaimportować je z pliku env.js
i skorzystać z nich tak, jak normalnie byłoby to możliwe. Jeżeli zaimportujesz obiekt ten na kliencie i spróbujesz skorzystać ze zmiennych serwera, wystąpi błąd runtime.
import { env } from "../../env.js";
// `env` jest w pełni typesafe i zapewnia autouzupełnianie
const dbUrl = env.DATABASE_URL;
import { env } from "../env.js";
// ❌ Wyrzuci to błąd runtime
const dbUrl = env.DATABASE_URL;
// ✅ To jest ok
const wsKey = env.NEXT_PUBLIC_WS_KEY;
.env.example
Ponieważ plik .env
nie jest wrzucany na system kontroli wersji, dołączamy także plik .env.example
, w którym - jesli chcesz - możesz zawrzeć kopię pliku .env
z usuniętymi secretami. Nie jest to wymagane, jednak polecamy trzymać aktualną kopię przykładowego pliku, aby ułatwić potencjalnym kontrybutorom rozpoczęcie pracy w ich środowisku.
Niektóre frameworki i narzędzia do budowania, takie jak Next.js, zalecają przechowywanie sekretnych wartości w pliku .env.local
i commitowanie plików .env
do projektu. Nie jest to przez nas jednak rekomendowane, ponieważ może to łatwo prowadzić do przypadkowego ujawnienia tych wartości. Polecamy natomiast przechowywanie sekretnych wartości w pliku .env
, trzymanie pliku tego w .gitignore
i commitowanie jedynie plików .env.example
.
Dodawanie Zmiennych Środowiskowych
Aby upewnić się, że twój projekt nie zbuduje się bez wymaganych zmiennych środowiskowych, będziesz musiał dodać nową zmienną w dwóch miejscach:
📄 .env
: Wprowadź swoją zmienną środ. tak, jak to zwykle robisz (np. KLUCZ=WARTOŚĆ
)
📄 env.js
: Dodaj odpowiadającą jej logikę walidacji definiując schemat Zod, np. KLUCZ: z.string()
. Następnie wykorzystaj obiekt process.env
w processEnv
, np. KEY: process.env.KEY
.
Opcjonalnie możesz zaktualizować plik .env.example
:
📄 .env.example
: Wprowadź swoją zmienną środowiskową, upewnij się jednak że nie nie posiada ona wartości, która jest sekretna, np. KLUCZ=WARTOŚĆ
lub KLUCZ=
Przykład
Chcę dodać mój token do API Twittera jako zmienną środowiskową po stronie serwera
- Dodaj zmienną środ. do pliku
.env
:
TWITTER_API_TOKEN=1234567890
- Dodaj zmienną środowiskową do pliku
env.js
:
export const server = z.object({
// ...
TWITTER_API_TOKEN: z.string(),
});
export const processEnv = {
// ...
TWITTER_API_TOKEN: process.env.TWITTER_API_TOKEN,
};
- opcjonalnie: Dodaj zmienną środowiskową do
.env.example
. Usuń jednak token.
TWITTER_API_TOKEN=