Frontend foundation built with Next.js 15 + TypeScript (ESM) — containerized and ready to connect with the existing Traefik infrastructure.
A minimal “Hello World” app designed to run locally as if it were in production.
FROM LOCALHOST TO PRODUCTION — BUILT LIKE A HACKER
This repository represents the frontend core of the Leonobitech full-stack architecture.
It uses Next.js 15 + TypeScript (ESM), runs in Docker, and is automatically discoverable by Traefik for HTTPS routing (app.localhost → frontend, api.localhost → backend).
It’s minimal by design — just enough to prove your infrastructure works end-to-end.
| Layer | Component | Purpose | 
|---|---|---|
| ⚛️ Next.js 15 | React framework | SSR + static pages + API routes | 
| 🦾 TypeScript (ESM) | Modern language support | Type safety + cleaner DX | 
| 🐳 Docker | Runtime isolation | Production-like local execution | 
| ⚡ Traefik 3.x | Reverse proxy | HTTPS, domain routing | 
| 🔐 mkcert | Local TLS | Trusted local HTTPS certificates | 
This project is designed to sit inside the same root alongside infra and backend:
root/
├─ assets/
├─ Docs/
│  ├─ README_BACKEND.md
│  └─ README_INFRA.md
├─ repositories/
│  ├─ core/          # backend (Node + TS + Hexagonal)
│  └─ frontend/      # <-- we will create this now
├─ traefik/
├─ .env
├─ .env.example
├─ docker-compose.yml
├─ docker-compose.local.yml
├─ docker-compose.prod.yml
├─ LICENSE
├─ Makefile
└─ README.md
From the root of your stack:
# 1) Ensure the parent folder exists
mkdir -p repositories
cd repositories
# 2) Create a Next.js app in "frontend"
#    (pick one: npm / pnpm / yarn)
npx create-next-app@latest frontend \
  --ts --eslint --app --src-dir false --tailwind \
  --use-npm --turbopack --import-alias "@/*"
# If you prefer pnpm:
pnpm dlx create-next-app@latest frontend \
  --ts --eslint --app --src-dir false --tailwind \
  --use-pnpm --turbopack --import-alias "@/*"
Why this?
- App Router and ESM by default.
- Tailwind ready out of the box.
- Turbopack for a faster dev server.
- Keeps the default alias @/*across the project.
We prefer CNA + shadcn init (more control) over opinionated templates.
cd repositories/frontend
npx shadcn@latest init- Select Base color: zincorslate( Optional )
npx shadcn@latest add button card input textarea select dialog sonnerapp/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});
const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});
export const metadata: Metadata = {
  title: "Frontend Core — Leonobitech",
  description: "Next.js + TypeScript + Tailwind + shadcn/ui minimal core",
};
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} min-h-dvh bg-background text-foreground antialiased p-6`}
      >
        {children}
      </body>
    </html>
  );
}app/page.tsx
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function HomePage() {
  return (
    <main className="max-w-xl mx-auto grid gap-4">
      <h1 className="text-2xl font-semibold">🚀 Frontend Core — Hello World</h1>
      <Card>
        <CardHeader>
          <CardTitle>Stack</CardTitle>
        </CardHeader>
        <CardContent className="space-y-2">
          <p>Next.js 15 + TypeScript + Tailwind + Turbopack + shadcn/ui</p>
          <Button>It works</Button>
        </CardContent>
      </Card>
    </main>
  );
}repositories/frontend/
├─ public/
├─ src/app/
│      ├─ page.tsx
│      └─ layout.tsx
├─ .gitignore
├─ components.json
├─ eslint.config.mjs
├─ next-env.d.ts
├─ next.config.ts
├─ package.json
├─ postcss.config.mjs
├─ README.md
└─ tsconfig.json
From repositories/frontend/:
npm run dev
# visit http://localhost:3000Build & start (prod mode, still without Docker):
npm run build && npm startCreate a Dockerfile inside repositories/frontend/:
# --- Builder ---
FROM node:22-alpine AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Runtime ---
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# curl para healthcheck
RUN apk add --no-cache curl
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "run", "start"]
.dockerignore
node_modules
.next
.git
.gitignore
Dockerfile
README.md
.env
.env.*
*.log
To make healthcheck faster and more stable, create a lightweight endpoint that always responds 200
// src/app/healthz/route.ts
export function GET() {
  return new Response("ok", {
    status: 200,
    headers: {
      "content-type": "text/plain; charset=utf-8",
      "cache-control": "no-store",
    },
  });
}healthcheck:
  test: ["CMD-SHELL", "curl -fsS http://localhost:3000/healthz >/dev/null || exit 1"]
  interval: 15s
  timeout: 3s
  retries: 3
  start_period: 10sWe’ll connect to the same Traefik network used by the rest of the stack (replace the network name if yours differs — e.g., proxy vs leonobitech-net). The example below assumes an external network named leonobitech-net and an .env with FRONTEND_DOMAIN=app.localhost.
frontend:
  build:
    context: ./repositories/frontend
    dockerfile: Dockerfile
  image: frontend:v1.0.0
  container_name: frontend
  restart: unless-stopped
  environment:
    - NODE_ENV=production
  networks:
    - leonobitech-net
  depends_on:
    traefik:
      condition: service_started
  # 🔍 Healthcheck: using a lightweight endpoint
  healthcheck:
    test:
      [
        "CMD-SHELL",
        "curl -fsS http://localhost:3000/healthz >/dev/null || exit 1",
      ]
    interval: 15s
    timeout: 3s
    retries: 3
    start_period: 10s
  labels:
    - "traefik.enable=true"
    # HTTPS router: https://app.localhost
    - "traefik.http.routers.frontend.rule=Host(`${FRONTEND_DOMAIN}`)"
    - "traefik.http.routers.frontend.entrypoints=websecure"
    - "traefik.http.routers.frontend.tls=true"
    # Forward to container port 3000
    - "traefik.http.services.frontend.loadbalancer.server.port=3000"
    # Optionally attach middlewares defined under traefik/dynamic
    # - "traefik.http.routers.frontend.middlewares=secure-strict@file"Ensure your root .env contains:
FRONTEND_DOMAIN=app.localhost
From the root:
docker compose up -d --build frontend
# or build the whole stack if Traefik is not up yet:
# docker compose up -d --build
# Options:
docker ps
docker logs -f frontendOpen:
- https://app.localhost → Frontend Core
- https://traefik.localhost → Dashboard (if enabled)
If you’re using mkcert from the infra repo, the cert will be trusted and the browser will show the lock icon.
- 404 from Traefik → Check labels and the external network name.
- TLS warning → Re-run mkcert and reload Traefik (see infra README).
- Port conflict 3000 → Stop local npm run devwhen testing the container.
- Not using your host → Confirm .env FRONTEND_DOMAINand the router rule.
“Production is not a deployment — it’s a mindset.”
This repository completes the local triad:
| Repo | Role | 
|---|---|
| 🧱 fullstack-infrastructure-blueprint | Traefik + mkcert base | 
| ⚙️ fullstack-backend-core | API core (Node + Express + Hexagonal) | 
| 🖥️ fullstack-frontend-core | Frontend (Next.js + TypeScript) | 
Together, they simulate a real production-grade full stack, entirely on your laptop.
docker compose up -d --build traefik core frontendAfter all three repos are up:
- https://traefik.localhost→ Traefik dashboard
- https://api.localhost→ Backend core
- https://app.localhost→ Frontend core
✅ Everything runs locally under HTTPS — just like in production.
frontend, nextjs, typescript, esm, docker, traefik, mkcert, production-like, leonobitech, fullstack, infrastructure
MIT © 2025 — Felix Figueroa @ Leonobitech
 🥷 Leonobitech Dev Team
 https://www.leonobitech.com
 Made with 🧠, 🥷, and Docker love 🐳 
🔥 This isn’t just a frontend. It’s your bridge between infrastructure and imagination.
