feat: downgrade tech stack to stable versions and integrate GA4 error monitoring

- Downgrade Next.js 16→14.2, React 19→18.3, Tailwind 4→3.4
- Add comprehensive GA4 error monitoring system
- Create Jenkins CI/CD pipeline with quality gates
- Fix build issues: ESLint, SWC conflict, config format
- Add documentation for deployment and error tracking
This commit is contained in:
张翔
2026-05-12 12:45:18 +08:00
parent f08874f5c4
commit 8840c4398a
24 changed files with 8567 additions and 3632 deletions
+168 -190
View File
@@ -1,215 +1,193 @@
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || '';
declare global {
interface Window {
gtag: (
command: string,
targetIdOrParams: string | Record<string, unknown>,
config?: Record<string, unknown>
) => void;
gtag: (...args: unknown[]) => void;
dataLayer: unknown[];
}
}
export const pageview = (url: string) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
});
}
};
export const event = (action: string, category: string, label?: string, value?: number) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
}
};
export const trackContactForm = (formData: Record<string, string>) => {
event('generate_lead', 'engagement', 'contact_form_submission');
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'contact_form', {
event_category: 'lead_generation',
event_label: formData.company || 'unknown_company',
company_size: formData.company ? 'provided' : 'not_provided',
});
}
};
export const trackButtonClick = (buttonName: string, location: string) => {
event('click', 'button', `${location}_${buttonName}`);
};
export const trackPageView = (pageTitle: string, _pagePath: string) => {
event('page_view', 'navigation', pageTitle);
};
export const trackConversion = (conversionName: string, value?: number) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'conversion', {
send_to: `${GA_MEASUREMENT_ID}/${conversionName}`,
value: value,
currency: 'CNY',
});
}
};
export const trackError = (errorType: string, errorMessage: string, fatal: boolean = false) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'exception', {
description: `${errorType}: ${errorMessage}`,
fatal: fatal,
});
}
};
export const trackPerformance = (metricName: string, value: number) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'web_vitals', {
name: metricName,
value: Math.round(value),
event_category: 'Web Vitals',
non_interaction: true,
});
}
};
export const trackScrollDepth = (percentage: number) => {
event('scroll', 'engagement', `${percentage}%`, percentage);
};
export const trackDownload = (fileName: string, fileType: string) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'file_download', {
event_category: 'downloads',
event_label: fileName,
file_extension: fileType,
});
}
};
export const trackOutboundLink = (url: string) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'click', {
event_category: 'outbound',
event_label: url,
transport_type: 'beacon',
});
}
};
export const trackVideo = (action: 'play' | 'pause' | 'complete' | 'progress', videoTitle: string, progress?: number) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', `video_${action}`, {
event_category: 'videos',
event_label: videoTitle,
video_percent: progress,
});
}
};
export const trackEngagement = (action: string, details?: Record<string, unknown>) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', action, {
event_category: 'engagement',
...details,
});
}
};
export const trackSectionView = (sectionName: string) => {
event('section_view', 'navigation', sectionName);
};
export const trackCaseView = (caseId: string, caseTitle: string) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'view_item', {
event_category: 'case_studies',
event_label: caseTitle,
item_id: caseId,
});
}
};
export const trackServiceInterest = (serviceName: string) => {
event('service_interest', 'engagement', serviceName);
};
export const trackProductView = (productId: string, productName: string) => {
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'view_item', {
event_category: 'products',
event_label: productName,
item_id: productId,
});
}
};
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || '';
export interface CookiePreferences {
necessary: boolean;
analytics: boolean;
marketing: boolean;
timestamp: number;
functionality: boolean;
timestamp?: number;
}
export const updateConsent = (granted: boolean) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('consent', 'update', {
analytics_storage: granted ? 'granted' : 'denied',
ad_storage: 'denied',
});
}
const DEFAULT_PREFERENCES: CookiePreferences = {
necessary: true,
analytics: true,
marketing: false,
functionality: true,
};
export const updateConsentDetailed = (preferences: CookiePreferences) => {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('consent', 'update', {
analytics_storage: preferences.analytics ? 'granted' : 'denied',
ad_storage: preferences.marketing ? 'granted' : 'denied',
functionality_storage: 'granted',
personalization_storage: preferences.marketing ? 'granted' : 'denied',
security_storage: 'granted',
});
export function getDefaultPreferences(): CookiePreferences {
return { ...DEFAULT_PREFERENCES };
}
if (preferences.analytics) {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: window.location.pathname + window.location.search,
page_title: document.title,
page_location: window.location.href,
});
}
}
};
export const getStoredPreferences = (): CookiePreferences | null => {
if (typeof window === 'undefined') {
return null;
}
export function getStoredPreferences(): CookiePreferences | null {
if (typeof window === 'undefined') return null;
try {
const stored = localStorage.getItem('cookie_preferences');
const stored = localStorage.getItem('novalon-cookie-preferences');
if (stored) {
return JSON.parse(stored) as CookiePreferences;
}
} catch {
return null;
} catch (e) {
console.warn('[Analytics] Failed to read cookie preferences:', e);
}
return null;
};
}
export const storePreferences = (preferences: CookiePreferences) => {
if (typeof window !== 'undefined') {
localStorage.setItem('cookie_preferences', JSON.stringify(preferences));
export function storePreferences(preferences: CookiePreferences): void {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(
'novalon-cookie-preferences',
JSON.stringify(preferences)
);
} catch (e) {
console.warn('[Analytics] Failed to store cookie preferences:', e);
}
};
}
export const getDefaultPreferences = (): CookiePreferences => ({
necessary: true,
analytics: false,
marketing: false,
timestamp: Date.now(),
});
export function updateConsentDetailed(preferences: CookiePreferences): void {
if (typeof window === 'undefined' || !window.gtag) return;
window.gtag('consent', 'update', {
analytics_storage: preferences.analytics ? 'granted' : 'denied',
ad_storage: preferences.marketing ? 'granted' : 'denied',
functionality_storage: preferences.functionality ? 'granted' : 'denied',
});
}
export function trackEvent(
action: string,
category: string,
label?: string,
value?: number
): void {
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
return;
}
window.gtag('event', action, {
event_category: category,
event_label: label,
event_value: value,
send_to: GA_MEASUREMENT_ID,
});
}
export function trackButtonClick(
buttonName: string,
_pageLocation?: string
): void {
trackEvent('button_click', 'engagement', buttonName, undefined);
}
export function trackError(
errorType: string,
message: string,
fatal: boolean = false,
additionalContext?: Record<string, string | number | boolean>
): void {
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
console.warn('[GA4] Error tracking not available:', { errorType, message });
return;
}
const errorData: Record<string, string | number | boolean> = {
description: `[${errorType}] ${message}`,
fatal: fatal ? 'true' : 'false',
url: typeof window !== 'undefined' ? window.location.href : '',
timestamp: new Date().toISOString(),
...additionalContext,
};
window.gtag('event', 'exception', {
...errorData,
send_to: GA_MEASUREMENT_ID,
});
if (process.env.NODE_ENV === 'development') {
console.log('[GA4] Error tracked:', errorData);
}
}
export function trackPageView(url: string, title: string): void {
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
return;
}
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
page_title: title,
});
}
export function trackPerformance(
metricName: string,
value: number,
category: string = 'web_vitals'
): void {
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
return;
}
window.gtag('event', metricName, {
event_category: category,
event_value: Math.round(value),
value: Math.round(value),
send_to: GA_MEASUREMENT_ID,
});
}
export function trackContactForm(
formData: { name: string; email: string; company: string } | string,
success?: boolean
): void {
if (typeof formData === 'string') {
trackEvent('form_submit', 'contact', formData, success ? 1 : 0);
} else {
trackEvent('form_submit', 'contact', formData.company, success !== false ? 1 : 0);
}
if (success !== false) {
trackConversion('contact_form_submit', 1);
}
}
export function trackConversion(conversionLabel: string, value?: number): void {
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
return;
}
window.gtag('event', 'conversion', {
send_to: GA_MEASUREMENT_ID,
transaction_id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
value: value || 1,
currency: 'CNY',
conversion_label: conversionLabel,
});
}
export function trackOutboundLink(url: string, _linkText?: string): void {
trackEvent('outbound_click', 'engagement', url);
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
window.gtag('event', 'click', {
event_category: 'outbound',
event_label: url,
transport_type: 'beacon',
send_to: GA_MEASUREMENT_ID,
});
}
}
export function trackScrollDepth(percentage: number, _maxScroll?: number): void {
trackEvent(`scroll_${percentage}`, 'engagement', `${percentage}%`);
}