Production portfolio SPA built with React 19 + TypeScript + Vite and Django REST Framework.
Public content, projects, certificates, contact details and legal texts are managed through Django Admin and exposed through REST endpoints. The frontend keeps safe built-in fallback text for singleton content, so temporary API failures do not make the main pages unusable.
Public site: https://p95max.dev
- Python 3.12
- Django 5.2
- Django REST Framework
- PostgreSQL
- Redis-compatible cache for shared throttling
- Gunicorn and WhiteNoise
- Jazzmin admin theme
- django-admin-sortable2
- drf-yasg
- Cloudflare Turnstile
- SMTP notifications
- React 19
- TypeScript
- Vite 7
- React Router
- Nginx static serving
- Cloudflare Turnstile widget
- consent-based self-hosted analytics
- Project and screenshot management through Django Admin.
- Certificate management with ordering, publication flags and previews.
- Contact form with validation, Turnstile, honeypot protection and layered DRF throttling.
- Contact-message workflow:
New,In progress,Done,Spam. - Consent-based self-hosted analytics.
- German legal pages at
/impressumand/datenschutz. - English and German UI localization.
- Admin-managed public singleton content with direct form navigation.
The following sections contain only one record. Clicking them in the Jazzmin sidebar opens the edit form directly instead of showing a one-row changelist.
Controls the homepage hero/About block:
- availability text in English and German
- greeting in English and German
- role in English and German
- description in English and German
- displayed name
- ordered technology stack
API:
GET /api/homepage-content/Controls the public contact links:
- GitHub
- Telegram
API:
GET /api/contact-details/Controls:
- trusted HTML for the Impressum page
- trusted HTML for the privacy-policy page
- all cookie-consent dialog labels and descriptions in English and German
The cookie-consent logic itself remains in frontend code. Admin users can change visible wording but cannot change consent storage, analytics activation or analytics-storage cleanup behavior.
API:
GET /api/legal-content/impressum_html and privacy_html are rendered as trusted administrator-provided HTML. Access to this admin section must therefore remain restricted to trusted staff.
The frontend requests singleton content from the backend. If the request fails or the response is incomplete:
- the homepage uses its built-in i18n content;
- the legal pages use built-in legal fallback text;
- the cookie dialog uses the existing i18n strings.
This keeps the public site functional during a backend restart or temporary API outage.
GET /api/projects/
GET /api/credentials/
GET /api/homepage-content/
GET /api/contact-details/
GET /api/legal-content/
POST /api/contact/
POST /api/analytics/
GET /api/health/
Swagger and ReDoc are available from the backend service:
/swagger/
/redoc/
Create backend/.env.dev:
DJANGO_SECRET_KEY=django-insecure-local-dev-key
DJANGO_DEBUG=True
DJANGO_ADMIN_URL=admin/
POSTGRES_DB=portfolio_db
POSTGRES_USER=portfolio_user
POSTGRES_PASSWORD=portfolio_password
DB_HOST=db
DB_PORT=5432
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=adminpass
ALLOWED_HOST=localhost
EXTRA_ALLOWED_HOSTS=127.0.0.1
CSRF_TRUSTED_ORIGINS=http://localhost:3000,http://localhost:8000
CORS_ALLOWED_ORIGINS=http://localhost:3000
TURNSTILE_SECRET=your-turnstile-secret
FRONTEND_BASE_URL=http://localhost:3000
BACKEND_BASE_URL=http://localhost:8000
EMAIL_HOST_USER=your_email@gmail.com
EMAIL_HOST_PASSWORD=your_gmail_app_password
NOTIFY_EMAILS=your_email@gmail.com
DISPLAY_TZ=Europe/Berlin
REDIS_URL=Create frontend/.env:
VITE_API_URL=http://localhost:8000
VITE_TURNSTILE_SITEKEY=your-turnstile-site-key
VITE_SNOW=autocd frontend
npm ci
npm run build
cd ..
docker compose up -d --buildApply migrations:
docker compose exec web poetry run python manage.py migrateBackend:
cd backend
poetry run python manage.py check
poetry run python manage.py makemigrations --check --dry-run
poetry run pytest --ds=config.test_settings --nomigrations -vv -ra --tb=shortFrontend:
cd frontend
npm ci
npm run lint
npm run test -- --run
npm run build- Analytics are disabled until explicit consent.
- Rejecting analytics removes the client analytics identifiers.
- Cloudflare Turnstile protects the contact form.
- Contact and analytics endpoints use DRF throttling.
- Production uses secure cookies, HTTPS redirect, HSTS and restricted CORS.
- Keep
DJANGO_DEBUG=Falseand use a non-obviousDJANGO_ADMIN_URLin production. - Use Redis in production when throttling must be shared across workers.
- Only trusted administrators should edit HTML fields in
Legal content.
Render configuration is stored in render.yaml.
Backend build:
root: backend
build: ./build.sh
runtime: Gunicorn
Frontend build:
root: frontend
build: npm ci && npm run build
publish: dist
Frontend variables must use the VITE_ prefix. Backend secrets must never be exposed through frontend environment variables.
MIT
Maksym Petrykin
Email: m.petrykin@gmx.de
Telegram: @max_p95