Jump to content

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.

env.js
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.

env.js
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.

env.js
// 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.

pages/api/hello.ts
import { env } from "../../env.js";

// `env` jest w pełni typesafe i zapewnia autouzupełnianie
const dbUrl = env.DATABASE_URL;
pages/index.tsx
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

  1. Dodaj zmienną środ. do pliku .env:
TWITTER_API_TOKEN=1234567890
  1. 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,
};
  1. opcjonalnie: Dodaj zmienną środowiskową do .env.example. Usuń jednak token.
TWITTER_API_TOKEN=