feat: Introduce NEXT_PUBLIC_TARGET build argument and abstract server-side error reporting to a dedicated service.
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 7s
Build & Deploy KLZ Cables / 🏗️ Build App (push) Successful in 4m2s
Build & Deploy KLZ Cables / 🏗️ Build Gatekeeper (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 11m56s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 43s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 2m7s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s

This commit is contained in:
2026-02-02 15:27:04 +01:00
parent 2a4cc76292
commit 0a797260e3
7 changed files with 43 additions and 21 deletions

View File

@@ -10,6 +10,11 @@
# ────────────────────────────────────────────────────────────────────────────
NODE_ENV=development
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# TARGET is used to differentiate between environments (testing, staging, production)
# NEXT_PUBLIC_TARGET makes this information available to the frontend
NEXT_PUBLIC_TARGET=development
# TARGET is used server-side
TARGET=development
# ────────────────────────────────────────────────────────────────────────────
# Analytics (Umami)

View File

@@ -234,6 +234,7 @@ jobs:
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \
--build-arg NEXT_PUBLIC_TARGET="$TARGET" \
--build-arg DIRECTUS_URL="$DIRECTUS_URL" \
-t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \
--cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache \

View File

@@ -27,11 +27,13 @@ ENV NEXT_TELEMETRY_DISABLED=1
ARG NEXT_PUBLIC_BASE_URL
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
ARG NEXT_PUBLIC_TARGET
ARG DIRECTUS_URL
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
ENV NEXT_PUBLIC_TARGET=$NEXT_PUBLIC_TARGET
ENV DIRECTUS_URL=$DIRECTUS_URL
# Validate environment variables during build

View File

@@ -18,10 +18,10 @@ function createConfig() {
return {
env: env.NODE_ENV,
target,
isProduction: target === 'production',
isProduction: target === 'production' || !target,
isStaging: target === 'staging',
isTesting: target === 'testing',
isDevelopment: target === 'development' || env.NODE_ENV === 'development',
isDevelopment: target === 'development',
baseUrl: env.NEXT_PUBLIC_BASE_URL,

View File

@@ -1,6 +1,6 @@
import { createDirectus, rest, authentication, readItems, readCollections } from '@directus/sdk';
import { config } from './config';
import * as Sentry from '@sentry/nextjs';
import { getServerAppServices } from './services/create-services.server';
const { url, adminEmail, password, token, proxyPath, internalUrl } = config.directus;
@@ -19,7 +19,7 @@ const shouldShowDevErrors = config.isTesting || config.isDevelopment;
*/
function formatError(error: any) {
if (shouldShowDevErrors) {
return error.message || 'An unexpected error occurred.';
return error.errors?.[0]?.message || error.message || 'An unexpected error occurred.';
}
return 'A system error occurred. Our team has been notified.';
}
@@ -33,7 +33,9 @@ export async function ensureAuthenticated() {
try {
await client.login(adminEmail, password);
} catch (e) {
Sentry.captureException(e);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(e, { part: 'directus_auth' });
}
console.error('Failed to authenticate with Directus:', e);
}
}
@@ -78,7 +80,9 @@ export async function getProducts(locale: string = 'de') {
);
return items.map((item) => mapDirectusProduct(item, locale));
} catch (error) {
Sentry.captureException(error);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(error, { part: 'directus_get_products' });
}
console.error('Error fetching products:', error);
return [];
}
@@ -104,7 +108,12 @@ export async function getProductBySlug(slug: string, locale: string = 'de') {
if (!items || items.length === 0) return null;
return mapDirectusProduct(items[0], locale);
} catch (error) {
Sentry.captureException(error);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(error, {
part: 'directus_get_product_by_slug',
slug,
});
}
console.error(`Error fetching product ${slug}:`, error);
return null;
}
@@ -117,7 +126,9 @@ export async function checkHealth() {
await ensureAuthenticated();
await client.request(readCollections());
} catch (e: any) {
Sentry.captureException(e);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(e, { part: 'directus_health_auth' });
}
console.error('Directus authentication failed during health check:', e);
return {
status: 'error',
@@ -133,7 +144,9 @@ export async function checkHealth() {
try {
await client.request(readItems('products', { limit: 1 }));
} catch (e: any) {
Sentry.captureException(e);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(e, { part: 'directus_health_schema' });
}
if (
e.message?.includes('does not exist') ||
e.code === 'INVALID_PAYLOAD' ||
@@ -142,7 +155,7 @@ export async function checkHealth() {
return {
status: 'error',
message: shouldShowDevErrors
? 'The "products" collection is missing or inaccessible. Please sync your data.'
? `The "products" collection is missing or inaccessible. Error: ${e.message || 'Unknown'}`
: 'Required data structures are currently unavailable.',
code: 'SCHEMA_MISSING',
};
@@ -150,7 +163,7 @@ export async function checkHealth() {
return {
status: 'error',
message: shouldShowDevErrors
? `Schema error: ${e.message}`
? `Schema error: ${e.errors?.[0]?.message || e.message || 'Unknown error'}`
: 'The data schema is currently misconfigured.',
code: 'SCHEMA_ERROR',
};
@@ -158,7 +171,9 @@ export async function checkHealth() {
return { status: 'ok', message: 'Directus is reachable and responding.' };
} catch (error: any) {
Sentry.captureException(error);
if (typeof window === 'undefined') {
getServerAppServices().errors.captureException(error, { part: 'directus_health_critical' });
}
console.error('Directus health check failed with unexpected error:', error);
return {
status: 'error',

View File

@@ -11,9 +11,7 @@ const preprocessEmptyString = (val: unknown) => (val === '' ? undefined : val);
export const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
NEXT_PUBLIC_BASE_URL: z.preprocess(preprocessEmptyString, z.string().url()),
NEXT_PUBLIC_TARGET: z
.enum(['development', 'testing', 'staging', 'production'])
.default('development'),
NEXT_PUBLIC_TARGET: z.enum(['development', 'testing', 'staging', 'production']).optional(),
// Analytics
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess(preprocessEmptyString, z.string().optional()),
@@ -50,7 +48,7 @@ export const envSchema = z.object({
INTERNAL_DIRECTUS_URL: z.preprocess(preprocessEmptyString, z.string().url().optional()),
// Deploy Target
TARGET: z.enum(['development', 'testing', 'staging', 'production']).default('development'),
TARGET: z.enum(['development', 'testing', 'staging', 'production']).optional(),
});
export type Env = z.infer<typeof envSchema>;

View File

@@ -13,7 +13,7 @@ export function getServerAppServices(): AppServices {
// Create logger first to log initialization
const logger = new PinoLoggerService('server');
logger.info('Initializing server application services', {
environment: getMaskedConfig(),
timestamp: new Date().toISOString(),
@@ -40,7 +40,9 @@ export function getServerAppServices(): AppServices {
: new NoopErrorReportingService();
if (config.errors.glitchtip.enabled) {
logger.info('GlitchTip error reporting service initialized');
logger.info('GlitchTip error reporting service initialized', {
dsnPresent: Boolean(config.errors.glitchtip.dsn),
});
} else {
logger.info('Noop error reporting service initialized (error reporting disabled)');
}
@@ -54,9 +56,8 @@ export function getServerAppServices(): AppServices {
});
singleton = new AppServices(analytics, errors, cache, logger);
logger.info('All application services initialized successfully');
return singleton;
}