{
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'
+
+ // Get all blog posts
+ const posts = await getAllPosts(false)
+
+ // Generate sitemap entries for blog posts
+ const blogPosts: MetadataRoute.Sitemap = posts.map(post => ({
+ url: `${baseUrl}/blog/${post.slug}`,
+ lastModified: new Date(post.frontmatter.date),
+ changeFrequency: 'monthly' as const,
+ priority: 0.8,
+ }))
+
+ // Static pages
+ const staticPages: MetadataRoute.Sitemap = [
+ {
+ url: baseUrl,
+ lastModified: new Date(),
+ changeFrequency: 'daily' as const,
+ priority: 1.0,
+ },
+ {
+ url: `${baseUrl}/blog`,
+ lastModified: new Date(),
+ changeFrequency: 'daily' as const,
+ priority: 0.9,
+ },
+ {
+ url: `${baseUrl}/about`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly' as const,
+ priority: 0.7,
+ },
+ ]
+
+ return [...staticPages, ...blogPosts]
+}
diff --git a/components/blog/markdown-renderer.tsx b/components/blog/markdown-renderer.tsx
index 78b3d66..07be1ac 100644
--- a/components/blog/markdown-renderer.tsx
+++ b/components/blog/markdown-renderer.tsx
@@ -2,6 +2,7 @@
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
+import rehypeSanitize from 'rehype-sanitize'
import rehypeRaw from 'rehype-raw'
import { OptimizedImage } from './OptimizedImage'
import { CodeBlock } from './code-block'
@@ -17,7 +18,17 @@ export default function MarkdownRenderer({ content, className = '' }: MarkdownRe
{
if (!src || typeof src !== 'string') return null
diff --git a/components/layout/breadcrumbs-schema.tsx b/components/layout/breadcrumbs-schema.tsx
index 6bfd7b2..171a56a 100644
--- a/components/layout/breadcrumbs-schema.tsx
+++ b/components/layout/breadcrumbs-schema.tsx
@@ -12,7 +12,7 @@ export function BreadcrumbsSchema({ items }: { items: BreadcrumbSchemaItem[] })
'@type': 'ListItem',
position: item.position,
name: item.name,
- item: `http://localhost:3000${item.item}`,
+ item: `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3030'}${item.item}`,
})),
}
diff --git a/content/blog/tech/articol-tehnic.md b/content/blog/tech/articol-tehnic.md
index 8f7ea39..5d509a7 100644
--- a/content/blog/tech/articol-tehnic.md
+++ b/content/blog/tech/articol-tehnic.md
@@ -11,6 +11,10 @@ tags: ['tech', 'test']
This is a test article for internal blog post linking.
+Imagine cooler:
+
+
+
## Content
You are reading the technical article that was linked from the example post.
diff --git a/content/blog/test-complet.md b/content/blog/test-complet.md
index 1c17ce8..5dbad7d 100644
--- a/content/blog/test-complet.md
+++ b/content/blog/test-complet.md
@@ -80,7 +80,7 @@ print(f"Result: {result}")
## Imagini
-
+
## Tabele
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 6ac4caa..185dc29 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -55,11 +55,19 @@ services:
volumes:
- ./data/logs:/app/logs
+ # Security options
+ security_opt:
+ - no-new-privileges:true # Prevent privilege escalation
+ # read_only: true # Commented - uncomment if you want extra hardening
+ # tmpfs: # Required if using read_only: true
+ # - /tmp
+ # - /app/.next/cache
+
# Health check configuration
# Docker monitors the application and marks it unhealthy if checks fail
# If container is unhealthy, restart policy will trigger a restart
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:3030/", "||", "exit", "1"]
+ test: ["CMD-SHELL", "curl -f http://localhost:3030/ || exit 1"]
interval: 30s # Check every 30 seconds
timeout: 10s # Wait up to 10 seconds for response
retries: 3 # Mark unhealthy after 3 consecutive failures
@@ -67,14 +75,14 @@ services:
# Resource limits for production
# Prevents container from consuming all server resources
- # deploy:
- # resources:
- # limits:
- # cpus: '1.0' # Maximum 1 CPU core
- # memory: 512M # Maximum 512MB RAM
- # reservations:
- # cpus: '0.25' # Reserve at least 0.25 CPU cores
- # memory: 256M # Reserve at least 256MB RAM
+ deploy:
+ resources:
+ limits:
+ cpus: '1.0' # Maximum 1 CPU core
+ memory: 512M # Maximum 512MB RAM
+ reservations:
+ cpus: '0.25' # Reserve at least 0.25 CPU cores
+ memory: 256M # Reserve at least 256MB RAM
# Network configuration
networks:
diff --git a/lib/env-validation.ts b/lib/env-validation.ts
new file mode 100644
index 0000000..86219c2
--- /dev/null
+++ b/lib/env-validation.ts
@@ -0,0 +1,47 @@
+/**
+ * Environment variable validation for production builds
+ * Ensures all required environment variables are set before deployment
+ */
+
+const requiredEnvVars = [
+ 'NEXT_PUBLIC_SITE_URL',
+ 'NODE_ENV',
+] as const
+
+const optionalEnvVars = [
+ 'PORT',
+ 'HOSTNAME',
+ 'NEXT_PUBLIC_GA_ID',
+] as const
+
+export function validateEnvironment() {
+ const missingVars: string[] = []
+
+ // Check required variables
+ for (const varName of requiredEnvVars) {
+ if (!process.env[varName]) {
+ missingVars.push(varName)
+ }
+ }
+
+ if (missingVars.length > 0) {
+ console.error('β Missing required environment variables:')
+ missingVars.forEach(varName => {
+ console.error(` - ${varName}`)
+ })
+ console.error('\nπ‘ Check .env.example for reference')
+ throw new Error('Environment validation failed')
+ }
+
+ // Log configuration (safe - no secrets)
+ if (process.env.NODE_ENV === 'production') {
+ console.log('β
Environment validation passed')
+ console.log(` - NEXT_PUBLIC_SITE_URL: ${process.env.NEXT_PUBLIC_SITE_URL}`)
+ console.log(` - PORT: ${process.env.PORT || '3030'}`)
+ }
+}
+
+// Run validation for production builds
+if (process.env.NODE_ENV === 'production') {
+ validateEnvironment()
+}
diff --git a/lib/markdown.ts b/lib/markdown.ts
index 86674f8..8424557 100644
--- a/lib/markdown.ts
+++ b/lib/markdown.ts
@@ -15,6 +15,15 @@ export function sanitizePath(inputPath: string): string {
if (normalized.includes('..') || path.isAbsolute(normalized)) {
throw new Error('Invalid path')
}
+
+ // CRITICAL: Verify resolved path stays within content directory
+ const resolvedPath = path.resolve(POSTS_PATH, normalized)
+ const allowedBasePath = path.resolve(POSTS_PATH)
+
+ if (!resolvedPath.startsWith(allowedBasePath)) {
+ throw new Error('Path traversal attempt detected')
+ }
+
return normalized
}
diff --git a/lib/remark-copy-images.ts b/lib/remark-copy-images.ts
index ac701c3..6fc273c 100644
--- a/lib/remark-copy-images.ts
+++ b/lib/remark-copy-images.ts
@@ -39,8 +39,9 @@ async function copyAndRewritePath(node: ImageNode, options: Options): Promise component size prop
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
+ // Cache optimized images for 30 days
+ minimumCacheTTL: 60 * 60 * 24 * 30,
+
+ // Allow SVG rendering (with security measures)
+ dangerouslyAllowSVG: true,
+ contentDispositionType: 'attachment',
+ contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
+
// Disable image optimization during build (optional)
// Uncomment if build times are too long
// unoptimized: false,
@@ -55,8 +70,6 @@ const nextConfig = {
// Performance Optimization
// ============================================
- // Enable SWC minification (faster than Terser)
- swcMinify: true,
// Compress static pages (reduces bandwidth)
compress: true,
@@ -95,9 +108,6 @@ const nextConfig = {
// ESLint during build
// Set to false to skip linting (not recommended)
- eslint: {
- // ignoreDuringBuilds: false,
- },
// ============================================
// Experimental Features (Next.js 16)
@@ -111,35 +121,94 @@ const nextConfig = {
static: 180,
},
+ // Optimize package imports for smaller bundles
+ optimizePackageImports: [
+ 'react-markdown',
+ 'rehype-raw',
+ 'rehype-sanitize',
+ 'remark-gfm',
+ ],
+
// Enable PPR (Partial Prerendering) - Next.js 16 feature
// Uncomment to enable (currently in beta)
// ppr: false,
},
// ============================================
- // Headers (Optional)
+ // Security Headers (PRODUCTION READY)
// ============================================
- // Custom headers for all routes
- // Note: Caddy/Nginx reverse proxy can also set these headers
- // Uncomment if you want Next.js to handle headers instead
- //
+ // Comprehensive security headers for public deployment
+ // Note: Caddy reverse proxy may also set these as backup
async headers() {
return [
{
source: '/:path*',
headers: [
+ // Prevent MIME type sniffing
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
+ // Prevent clickjacking
{
key: 'X-Frame-Options',
value: 'DENY',
},
+ // XSS Protection (legacy browsers)
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
+ // HSTS - Force HTTPS for 1 year
+ {
+ key: 'Strict-Transport-Security',
+ value: 'max-age=31536000; includeSubDomains; preload',
+ },
+ // Referrer Policy - Protect user privacy
+ {
+ key: 'Referrer-Policy',
+ value: 'strict-origin-when-cross-origin',
+ },
+ // Permissions Policy - Disable unnecessary browser features
+ {
+ key: 'Permissions-Policy',
+ value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
+ },
+ // Content Security Policy - Restrict resource loading
+ // Note: Next.js requires 'unsafe-inline' for styled-jsx
+ {
+ key: 'Content-Security-Policy',
+ value: [
+ "default-src 'self'",
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
+ "style-src 'self' 'unsafe-inline'",
+ "img-src 'self' data: https:",
+ "font-src 'self' data:",
+ "connect-src 'self'",
+ "frame-ancestors 'none'",
+ "base-uri 'self'",
+ "form-action 'self'",
+ ].join('; '),
+ },
+ ],
+ },
+ // Aggressive caching for static assets
+ {
+ source: '/_next/static/:path*',
+ headers: [
+ {
+ key: 'Cache-Control',
+ value: 'public, max-age=31536000, immutable',
+ },
+ ],
+ },
+ {
+ source: '/images/:path*',
+ headers: [
+ {
+ key: 'Cache-Control',
+ value: 'public, max-age=31536000, immutable',
+ },
],
},
]
diff --git a/package-lock.json b/package-lock.json
index 27b76ce..8404ee6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,6 @@
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
- "@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.21",
"gray-matter": "^4.0.3",
"next": "^16.0.1",
@@ -22,11 +21,11 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
- "react-syntax-highlighter": "^16.1.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
+ "sharp": "^0.34.5",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"unist-util-visit": "^5.0.0"
@@ -34,6 +33,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
+ "@next/bundle-analyzer": "^16.0.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^9.39.1",
@@ -268,15 +268,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/runtime": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
- "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -325,6 +316,16 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz",
@@ -622,7 +623,6 @@
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=18"
}
@@ -1140,6 +1140,16 @@
"@tybys/wasm-util": "^0.10.0"
}
},
+ "node_modules/@next/bundle-analyzer": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-16.0.3.tgz",
+ "integrity": "sha512-6Xo8f8/ZXtASfTPa6TH1aUn+xDg9Pkyl1YHVxu+89cVdLH7MnYjxv3rPOfEJ9BwCZCU2q4Flyw5MwltfD2pGbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "webpack-bundle-analyzer": "4.10.1"
+ }
+ },
"node_modules/@next/env": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.1.tgz",
@@ -1375,6 +1385,13 @@
"url": "https://opencollective.com/pkgr"
}
},
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1740,12 +1757,6 @@
"undici-types": "~7.16.0"
}
},
- "node_modules/@types/prismjs": {
- "version": "1.26.5",
- "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
- "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
- "license": "MIT"
- },
"node_modules/@types/react": {
"version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
@@ -1755,15 +1766,6 @@
"csstype": "^3.0.2"
}
},
- "node_modules/@types/react-syntax-highlighter": {
- "version": "15.5.13",
- "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
- "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
- "license": "MIT",
- "dependencies": {
- "@types/react": "*"
- }
- },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -2303,6 +2305,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2870,6 +2885,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2978,6 +3003,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -3110,6 +3142,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/duplexer": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.248",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.248.tgz",
@@ -4091,19 +4130,6 @@
"reusify": "^1.0.4"
}
},
- "node_modules/fault": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
- "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
- "license": "MIT",
- "dependencies": {
- "format": "^0.2.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4184,14 +4210,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/format": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
- "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
- "engines": {
- "node": ">=0.4.x"
- }
- },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -4420,6 +4438,22 @@
"node": ">=6.0"
}
},
+ "node_modules/gzip-size": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
+ "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "duplexer": "^0.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -4690,20 +4724,12 @@
"hermes-estree": "0.25.1"
}
},
- "node_modules/highlight.js": {
- "version": "10.7.3",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
- "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/highlightjs-vue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
- "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
- "license": "CC0-1.0"
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/html-url-attributes": {
"version": "3.0.1",
@@ -5105,6 +5131,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -5299,9 +5335,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
@@ -5722,20 +5758,6 @@
"loose-envify": "cli.js"
}
},
- "node_modules/lowlight": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
- "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
- "license": "MIT",
- "dependencies": {
- "fault": "^1.0.0",
- "highlight.js": "~10.7.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -6658,6 +6680,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6933,6 +6965,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/opener": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)",
+ "bin": {
+ "opener": "bin/opener-bin.js"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -7193,15 +7235,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/prismjs": {
- "version": "1.30.0",
- "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
- "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -7310,26 +7343,6 @@
"react": ">=18"
}
},
- "node_modules/react-syntax-highlighter": {
- "version": "16.1.0",
- "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
- "integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.4",
- "highlight.js": "^10.4.1",
- "highlightjs-vue": "^1.0.0",
- "lowlight": "^1.17.0",
- "prismjs": "^1.30.0",
- "refractor": "^5.0.0"
- },
- "engines": {
- "node": ">= 16.20.2"
- },
- "peerDependencies": {
- "react": ">= 0.14.0"
- }
- },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -7353,22 +7366,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/refractor": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz",
- "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/prismjs": "^1.0.0",
- "hastscript": "^9.0.0",
- "parse-entities": "^4.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -7655,7 +7652,6 @@
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -7719,7 +7715,6 @@
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
- "optional": true,
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
@@ -7857,6 +7852,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sirv": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+ "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -8225,6 +8235,16 @@
"node": ">=8.0"
}
},
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -8663,6 +8683,47 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/webpack-bundle-analyzer": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz",
+ "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@discoveryjs/json-ext": "0.5.7",
+ "acorn": "^8.0.4",
+ "acorn-walk": "^8.0.0",
+ "commander": "^7.2.0",
+ "debounce": "^1.2.1",
+ "escape-string-regexp": "^4.0.0",
+ "gzip-size": "^6.0.0",
+ "html-escaper": "^2.0.2",
+ "is-plain-object": "^5.0.0",
+ "opener": "^1.5.2",
+ "picocolors": "^1.0.0",
+ "sirv": "^2.0.3",
+ "ws": "^7.3.1"
+ },
+ "bin": {
+ "webpack-bundle-analyzer": "lib/bin/analyzer.js"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -8778,6 +8839,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index 38b53f3..15e497b 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,14 @@
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
- "validate-posts": "node scripts/validate-posts.js"
+ "validate-posts": "node scripts/validate-posts.js",
+ "build:production": "NODE_ENV=production npm run build",
+ "validate:env": "node -e \"require('./lib/env-validation').validateEnvironment()\"",
+ "docker:build": "docker build -t mypage:latest -f Dockerfile.nextjs .",
+ "docker:run": "docker run -p 3030:3030 --env-file .env.production mypage:latest",
+ "analyze": "ANALYZE=true npm run build",
+ "analyze:server": "BUNDLE_ANALYZE=server npm run build",
+ "analyze:browser": "BUNDLE_ANALYZE=browser npm run build"
},
"repository": {
"type": "git",
@@ -25,7 +32,6 @@
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^24.10.0",
"@types/react": "^19.2.2",
- "@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.21",
"gray-matter": "^4.0.3",
"next": "^16.0.1",
@@ -34,11 +40,11 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
- "react-syntax-highlighter": "^16.1.0",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
+ "sharp": "^0.34.5",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"unist-util-visit": "^5.0.0"
@@ -46,6 +52,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
+ "@next/bundle-analyzer": "^16.0.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^9.39.1",
diff --git a/public/blog/tech/articol-tehnic.md b/public/blog/tech/articol-tehnic.md
new file mode 100644
index 0000000..b6401bb
--- /dev/null
+++ b/public/blog/tech/articol-tehnic.md
@@ -0,0 +1,20 @@
+---
+title: 'Technical Article'
+description: 'A technical article to test internal links'
+date: '2025-01-10'
+author: 'John Doe'
+category: 'Tech'
+tags: ['tech', 'test']
+---
+
+# Technical Article
+
+This is a test article for internal blog post linking.
+
+Imagine cooler:
+
+
+
+## Content
+
+You are reading the technical article that was linked from the example post.