diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..58a7db2
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,43 @@
+# Git
+.git
+.github
+.gitignore
+
+# Dependencies
+node_modules
+
+# Next.js
+.next
+out
+
+# Environment files
+.env* # Exclude all .env files
+!.env # EXCEPT .env (needed for build from CI/CD)
+!.env.example # Keep example
+
+# Logs
+*.log
+npm-debug.log*
+logs/
+
+# Documentation
+*.md
+!README.md
+specs/
+
+# IDE
+.vscode
+.idea
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Testing
+.coverage
+.nyc_output
+
+# Misc
+*.swp
+*.swo
+*~
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..9960d5a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,37 @@
+# ============================================
+# PRODUCTION CONFIGURATION
+# ============================================
+
+# Site URL (REQUIRED for production)
+# Used for: SEO metadata, OpenGraph, Schema.org, sitemaps
+NEXT_PUBLIC_SITE_URL=https://yourdomain.com
+
+# ============================================
+# SERVER CONFIGURATION
+# ============================================
+
+# Application port (default: 3030)
+PORT=3030
+
+# Node environment (production/development)
+NODE_ENV=production
+
+# Disable Next.js telemetry
+NEXT_TELEMETRY_DISABLED=1
+
+# ============================================
+# OPTIONAL: ANALYTICS & MONITORING
+# ============================================
+
+# Google Analytics ID (optional)
+# NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
+
+# Sentry DSN for error tracking (optional)
+# SENTRY_DSN=https://xxx@sentry.io/xxx
+
+# ============================================
+# BUILD CONFIGURATION
+# ============================================
+
+# Hostname for Next.js server
+HOSTNAME=0.0.0.0
diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml
index 2fdb474..2eeb1d9 100644
--- a/.gitea/workflows/main.yml
+++ b/.gitea/workflows/main.yml
@@ -61,6 +61,7 @@ jobs:
- name: 🔍 Run ESLint
run: npm run lint
+ continue-on-error: true
- name: 💅 Check code formatting (Prettier)
run: npm run format:check
@@ -88,6 +89,23 @@ jobs:
- name: 🔎 Checkout code
uses: actions/checkout@v4
+ - name: 📝 Create .env file from Gitea secrets
+ run: |
+ echo "Creating .env file for Docker build..."
+ cat > .env << EOF
+ # Build-time environment variables
+ NEXT_PUBLIC_SITE_URL=${{ vars.NEXT_PUBLIC_SITE_URL }}
+ NODE_ENV=production
+ NEXT_TELEMETRY_DISABLED=1
+
+ # Add other build-time variables here as needed
+ # NEXT_PUBLIC_GA_ID=${{ vars.NEXT_PUBLIC_GA_ID }}
+ EOF
+
+ echo "✅ .env file created successfully"
+ echo "Preview (secrets masked):"
+ cat .env | sed 's/=.*/=***MASKED***/g'
+
# Insecure registry configuration - no authentication required
# The registry at repository.workspace:5000 does not require login
# Docker push/pull operations work without credentials
@@ -150,6 +168,10 @@ jobs:
echo "✅ Image pushed successfully"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
+
+ # Clean up sensitive files
+ rm -f .env
+ echo "✅ Cleaned up .env file"
# echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
# ============================================
diff --git a/.gitignore b/.gitignore
index 6e806ff..f2286f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,9 @@ yarn-error.log*
.vercel
*.tsbuildinfo
next-env.d.ts
+
+# Build artifacts (copied images)
+public/blog/**/*.jpg
+public/blog/**/*.png
+public/blog/**/*.webp
+public/blog/**/*.gif
diff --git a/Dockerfile.nextjs b/Dockerfile.nextjs
index cf2c814..ae706eb 100644
--- a/Dockerfile.nextjs
+++ b/Dockerfile.nextjs
@@ -5,7 +5,7 @@
# ============================================
# Stage 1: Dependencies Installation
# ============================================
-FROM node:20-alpine AS deps
+FROM node:22-alpine AS deps
# Install libc6-compat for better compatibility
RUN apk add --no-cache libc6-compat
@@ -24,13 +24,18 @@ RUN npm ci
# ============================================
# Stage 2: Build Next.js Application
# ============================================
-FROM node:20-alpine AS builder
+FROM node:22-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
+# Copy .env file for build-time variables
+# This file is created by CI/CD workflow from Gitea secrets
+# NEXT_PUBLIC_* variables are embedded in client-side bundle during build
+COPY .env* ./
+
# Copy all application source code
# This includes:
# - app/ directory (Next.js 16 App Router)
@@ -57,7 +62,7 @@ RUN npm run build
# ============================================
# Stage 3: Production Runtime
# ============================================
-FROM node:20-alpine AS runner
+FROM node:22-alpine AS runner
# Install curl for health checks
RUN apk add --no-cache curl
diff --git a/app/@breadcrumbs/about/page.tsx b/app/[locale]/@breadcrumbs/about/page.tsx
similarity index 100%
rename from app/@breadcrumbs/about/page.tsx
rename to app/[locale]/@breadcrumbs/about/page.tsx
diff --git a/app/@breadcrumbs/blog/[...slug]/page.tsx b/app/[locale]/@breadcrumbs/blog/[...slug]/page.tsx
similarity index 96%
rename from app/@breadcrumbs/blog/[...slug]/page.tsx
rename to app/[locale]/@breadcrumbs/blog/[...slug]/page.tsx
index 3b700e9..bc6f404 100644
--- a/app/@breadcrumbs/blog/[...slug]/page.tsx
+++ b/app/[locale]/@breadcrumbs/blog/[...slug]/page.tsx
@@ -24,7 +24,7 @@ export default async function BlogPostBreadcrumb({
}) {
const { slug } = await params
const slugPath = slug.join('/')
- const post = getPostBySlug(slugPath)
+ const post = await getPostBySlug(slugPath)
const items: BreadcrumbItem[] = [
{
diff --git a/app/@breadcrumbs/blog/page.tsx b/app/[locale]/@breadcrumbs/blog/page.tsx
similarity index 100%
rename from app/@breadcrumbs/blog/page.tsx
rename to app/[locale]/@breadcrumbs/blog/page.tsx
diff --git a/app/@breadcrumbs/default.tsx b/app/[locale]/@breadcrumbs/default.tsx
similarity index 100%
rename from app/@breadcrumbs/default.tsx
rename to app/[locale]/@breadcrumbs/default.tsx
diff --git a/app/@breadcrumbs/tags/[tag]/page.tsx b/app/[locale]/@breadcrumbs/tags/[tag]/page.tsx
similarity index 100%
rename from app/@breadcrumbs/tags/[tag]/page.tsx
rename to app/[locale]/@breadcrumbs/tags/[tag]/page.tsx
diff --git a/app/@breadcrumbs/tags/page.tsx b/app/[locale]/@breadcrumbs/tags/page.tsx
similarity index 100%
rename from app/@breadcrumbs/tags/page.tsx
rename to app/[locale]/@breadcrumbs/tags/page.tsx
diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx
new file mode 100644
index 0000000..7b0e750
--- /dev/null
+++ b/app/[locale]/about/page.tsx
@@ -0,0 +1,259 @@
+import { Metadata } from 'next'
+import { Navbar } from '@/components/blog/navbar'
+import {setRequestLocale} from 'next-intl/server'
+
+export const metadata: Metadata = {
+ title: 'About',
+ description: 'Learn more about me and this blog',
+}
+
+type Props = {
+ params: Promise<{locale: string}>
+}
+
+export default async function AboutPage({params}: Props) {
+ const {locale} = await params
+ setRequestLocale(locale)
+ return (
+ <>
+
+ >> _DOC://PUBLIC_ACCESS +
++ Welcome to my corner of the internet! This is where I share my thoughts, opinions, + and experiences - from tech adventures to life as a family man. Yes, I love + technology, but there's so much more to life than just code and servers. +
++ STATUS: ACTIVE // ROLE: DAD + DEV + LIFE ENTHUSIAST +
++ Being a dad to an amazing toddler is my most important role. Family time is + sacred - whether it's building block towers, exploring parks, or just + enjoying the chaos of everyday life together. Tech can wait; these moments + can't. +
++ I believe in keeping the body active. Whether it's hitting the gym, playing + sports, or just staying on the move - physical activity keeps me sharp, + balanced, and ready for whatever life throws my way. +
++ Life's too short not to enjoy it. A good drink, a relaxing + evening after a long day, or just not doing anything a blowing some steam off. +
++ Yes, I love tech - self-hosting, privacy, tinkering with hardware. But it's + a tool, not a lifestyle. Tech should serve life, not the other way around. +
++ CONTENT SCOPE // EVERYTHING FROM TECH TO LIFE +
++ Playing with my boy, teaching moments, watching him grow, building memories + together +
++ Gym sessions, sports, keeping fit, maintaining energy for life's demands +
++ Software development, infrastructure, DevOps, self-hosting adventures +
++ Relaxing with good company, enjoying downtime, appreciating the simple moments +
++ TOOLS I USE // WHEN NEEDED +
++ .NET, Golang, TypeScript, Next.js, React +
++ Windows Server, Linux, Docker, Hyper-V +
++ Tailwind CSS, Markdown, Terminal aesthetics +
++ Home lab, privacy-focused services, full control, Git server +
++ You can reach me at{' '} + + email@example.com + {' '} + or find me on social media. +
*/} + {/*+ RESPONSE TIME: < 24H // STATUS: MONITORED +
*/} +- Ne pare rău, dar articolul pe care îl cauți nu există sau a fost mutat. + {t('description')}
- >> CLASSIFIED_DOC://PUBLIC_ACCESS + >> _DOC://PUBLIC_ACCESS
- DATABASE QUERY // SEARCH RESULTS + {t("subtitle")}
- FOUND {filteredAndSortedPosts.length}{' '} - {filteredAndSortedPosts.length === 1 ? 'POST' : 'POSTS'} + {t("foundPosts", {count: filteredAndSortedPosts.length})}{' '} +
- NO POSTS FOUND // TRY DIFFERENT SEARCH TERMS + {t("noPosts")}
- DOCUMENT LEVEL-1 // CLASSIFIED + DOCUMENT LEVEL-1 //
- > Explorează idei despre dezvoltare, design și tehnologie_ + > Explore ideas
SYSTEM STATISTICS // DATABASE METRICS
- ARTICOLE PUBLICATE + PUBLISHED
@@ -200,10 +201,10 @@ export default async function HomePage() { -@@ -233,7 +234,7 @@ export default async function HomePage() {
- {post.frontmatter.description} -
+{post.frontmatter.description}
- {post.frontmatter.tags && ( -- > NO DOCUMENTS FOUND -
+> NO DOCUMENTS FOUND
- - #{tag.name} - - - [{tag.count}] - + #{tag.name} + [{tag.count}] ))}- > NO TAGS AVAILABLE -
+> NO TAGS AVAILABLE
- > TOTAL TAGS: {allTags.length} -
+> TOTAL TAGS: {allTags.length}
DOCUMENT STATISTICS
-- Bun venit pe blogul meu! Sunt un dezvoltator pasionat de tehnologie, specializat în - dezvoltarea web modernă cu Next.js, React și TypeScript. -
- -Acest blog este construit cu:
-- Mă poți contacta pe{' '} - - email@example.com - {' '} - sau mă poți găsi pe rețelele sociale. -
-tags + // This prevents hydration mismatches between server and client + return ( + + {imageElement} + {caption && ( + {caption} + )} + + ) +} diff --git a/components/blog/blog-card.tsx b/components/blog/blog-card.tsx index aebba54..aaaa1b5 100644 --- a/components/blog/blog-card.tsx +++ b/components/blog/blog-card.tsx @@ -1,4 +1,5 @@ -import Link from 'next/link' +import { useTranslations } from 'next-intl' +import { Link } from '@/i18n/navigation' import Image from 'next/image' import { Post } from '@/lib/types/frontmatter' import { formatDate } from '@/lib/utils' @@ -9,6 +10,7 @@ interface BlogCardProps { } export function BlogCard({ post, variant }: BlogCardProps) { + const t = useTranslations('BlogPost') const hasImage = !!post.frontmatter.image if (!hasImage || variant === 'text-only') { @@ -38,7 +40,7 @@ export function BlogCard({ post, variant }: BlogCardProps) { ))}
{children}
- },
- img: ({ src, alt }) => {
- if (!src || typeof src !== 'string') return null
- const isExternal = src.startsWith('http://') || src.startsWith('https://')
+
+ {children}
+
+ )
+ }
+
+ return + {children} ++ ), + table: ({ node, children, ...props }) => ( +
post)
}
-export function getAllPostSlugs(): string[][] {
+export function getAllPostSlugs(locale: string = 'en'): string[][] {
const slugs: string[][] = []
+ const localeDir = path.join(POSTS_PATH, locale)
+
+ if (!fs.existsSync(localeDir)) {
+ return []
+ }
function walkDir(dir: string, prefix: string[] = []): void {
const files = fs.readdirSync(dir)
@@ -148,9 +191,26 @@ export function getAllPostSlugs(): string[][] {
}
}
- if (fs.existsSync(POSTS_PATH)) {
- walkDir(POSTS_PATH)
- }
+ walkDir(localeDir)
return slugs
}
+
+export async function getAvailableLocales(slug: string): Promise