Skip to content

Commit 0fd0fef

Browse files
committed
enhancement(env): add metrics configuration and validation
1 parent dd1054b commit 0fd0fef

6 files changed

Lines changed: 89 additions & 6 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ NEXT_PUBLIC_FARO_APP_NAME=next-frontend
66
NEXT_PUBLIC_FARO_APP_NAMESPACE=nextjs-example
77
NEXT_PUBLIC_FARO_APP_VERSION=1.0.0
88
NEXT_PUBLIC_APP_ENV=development
9+
NEXT_PUBLIC_METRICS_ENABLED=false
910

1011
# Next App BACKEND Instrumentation
1112
## Example assumes that the collector is running on the same machine

.env.production

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/v1
2+
PORT=3000
3+
# Next App FRONTEND Instrumentation
4+
NEXT_PUBLIC_FARO_URL=http://localhost:12347/collect
5+
NEXT_PUBLIC_FARO_APP_NAME=ttopen-frontend
6+
NEXT_PUBLIC_FARO_APP_NAMESPACE=ttopen-front
7+
NEXT_PUBLIC_FARO_APP_VERSION=1.0.0
8+
NEXT_PUBLIC_APP_ENV=development
9+
NEXT_PUBLIC_METRICS_ENABLED=true
10+
11+
# Next App BACKEND Instrumentation
12+
## Example assumes that the collector is running on the same machine
13+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
14+
## Force protobuf
15+
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
16+
## Set Backend service name
17+
OTEL_SERVICE_NAME=next-backend
18+
## Customize resource attributes, namespace is a recommended attribute
19+
OTEL_RESOURCE_ATTRIBUTES=service.namespace=nextjs-ttopen
20+
21+
# OTel collector
22+
GRAFANA_CLOUD_USERNAME=
23+
GRAFANA_CLOUD_API_KEY=
24+
GRAFANA_CLOUD_ENDPOINT=

Dockerfile.prod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ARG NEXT_PUBLIC_API_BASE_URL
2020
ARG NEXT_PUBLIC_FARO_URL
2121
ARG NEXT_PUBLIC_FARO_APP_VERSION
2222
ARG NEXT_PUBLIC_APP_ENV
23+
ARG NEXT_PUBLIC_METRICS_ENABLED
2324
ARG SKIP_ENV_VALIDATION=false
2425

2526
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
@@ -29,7 +30,8 @@ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
2930
NEXT_PUBLIC_FARO_APP_VERSION=$NEXT_PUBLIC_FARO_APP_VERSION \
3031
NEXT_PUBLIC_APP_ENV=$NEXT_PUBLIC_APP_ENV \
3132
SKIP_ENV_VALIDATION=$SKIP_ENV_VALIDATION \
32-
NEXT_TELEMETRY_DISABLED=1
33+
NEXT_TELEMETRY_DISABLED=1 \
34+
NEXT_PUBLIC_METRICS_ENABLED=$NEXT_PUBLIC_METRICS_ENABLED
3335

3436
COPY --from=deps /app/node_modules ./node_modules
3537
COPY . .

src/shared/config/env.client.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { z } from 'zod/v4';
2+
3+
export const envSchemaClient = z.object({
4+
NEXT_PUBLIC_API_BASE_URL: z.url('NEXT_PUBLIC_API_BASE_URL должен быть валидным URL'),
5+
NEXT_PUBLIC_FARO_URL: z.url(
6+
'NEXT_PUBLIC_FARO_URL должен быть валидным URL (например, http://alloy:12347/collect)'
7+
),
8+
NEXT_PUBLIC_FARO_APP_NAME: z
9+
.string({
10+
error: 'Имя приложения для Faro обязательно',
11+
})
12+
.min(1, 'Имя приложения не может быть пустым'),
13+
NEXT_PUBLIC_FARO_APP_NAMESPACE: z
14+
.string({
15+
error: 'Namespace приложения обязателен',
16+
})
17+
.min(1, 'Namespace не может быть пустым'),
18+
NEXT_PUBLIC_FARO_APP_VERSION: z.string().default('1.0.0'),
19+
NEXT_PUBLIC_APP_ENV: z
20+
.string({
21+
error: 'Окружение (APP_ENV) обязательно',
22+
})
23+
.min(1, 'Окружение не может быть пустым'),
24+
NEXT_PUBLIC_METRICS_ENABLED: z
25+
.enum(['true', 'false'], {
26+
error: 'NEXT_PUBLIC_METRICS_ENABLED - обязателен',
27+
})
28+
.transform((v) => v === 'true'),
29+
});
30+
31+
const _env = envSchemaClient.safeParse({
32+
NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL,
33+
NEXT_PUBLIC_FARO_URL: process.env.NEXT_PUBLIC_FARO_URL,
34+
NEXT_PUBLIC_FARO_APP_NAME: process.env.NEXT_PUBLIC_FARO_APP_NAME,
35+
NEXT_PUBLIC_FARO_APP_NAMESPACE: process.env.NEXT_PUBLIC_FARO_APP_NAMESPACE,
36+
NEXT_PUBLIC_FARO_APP_VERSION: process.env.NEXT_PUBLIC_FARO_APP_VERSION,
37+
NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV,
38+
NEXT_PUBLIC_METRICS_ENABLED: process.env.NEXT_PUBLIC_METRICS_ENABLED,
39+
});
40+
41+
if (!_env.success) {
42+
console.error('❌ [ENV VALIDATION ERROR]:', z.treeifyError(_env.error).properties);
43+
if (typeof window === 'undefined') {
44+
throw new Error('Client env validation failed on server-side rendering');
45+
}
46+
}
47+
48+
export type ClientEnv = z.infer<typeof envSchemaClient>;
49+
50+
export const env: ClientEnv = _env.success ? _env.data : ({} as ClientEnv);

src/shared/config/env.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { z } from 'zod/v4';
33
const isServer = typeof window === 'undefined';
44
const isBuild = process.env.SKIP_ENV_VALIDATION === 'true';
55

6+
const metricEnabledSchema = z.enum(['true', 'false'], {
7+
error: 'NEXT_PUBLIC_METRICS_ENABLED - обязателен',
8+
});
9+
610
const envSchemaServer = z.object({
711
NODE_ENV: z
812
.enum(['development', 'production', 'test'], {
@@ -35,11 +39,7 @@ const envSchemaServer = z.object({
3539
});
3640

3741
const envSchemaClient = z.object({
38-
NEXT_PUBLIC_API_BASE_URL: z
39-
.string({
40-
error: 'API Base URL обязателен',
41-
})
42-
.url('NEXT_PUBLIC_API_BASE_URL должен быть валидным URL'),
42+
NEXT_PUBLIC_API_BASE_URL: z.url('NEXT_PUBLIC_API_BASE_URL должен быть валидным URL'),
4343
NEXT_PUBLIC_FARO_URL: z
4444
.string({
4545
error: 'URL для Faro (Alloy) обязателен',
@@ -61,6 +61,10 @@ const envSchemaClient = z.object({
6161
error: 'Окружение (APP_ENV) обязательно',
6262
})
6363
.min(1, 'Окружение не может быть пустым'),
64+
NEXT_PUBLIC_METRICS_ENABLED:
65+
process.env.NODE_ENV === 'development'
66+
? metricEnabledSchema.default('false').transform((v) => v === 'true')
67+
: metricEnabledSchema.transform((v) => v === 'true'),
6468
});
6569

6670
const envSchema = envSchemaClient.extend(envSchemaServer.shape);

src/shared/config/metrics/FrontendObservability.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import { useEffect } from 'react';
44
import { faro, getWebInstrumentations, initializeFaro } from '@grafana/faro-web-sdk';
55
import { TracingInstrumentation } from '@grafana/faro-web-tracing';
6+
import { env } from '../env.client';
67

78
let isFaroInitialized = false;
89

910
export default function FrontendObservability() {
1011
useEffect(() => {
12+
if (!env.NEXT_PUBLIC_METRICS_ENABLED) return;
1113
const initializeWhenIdle = () => {
1214
if (isFaroInitialized || faro.api) {
1315
return;

0 commit comments

Comments
 (0)