A fast, static portfolio site for AI/ML & MLOps work. Built with plain HTML/CSS/JS, deployed as a Static Site on Render with pretty URLs, SEO basics, and a no-backend contact form via Formspree. Helped to setup your own website? You can buy me a coffee to support my work βοΈ.
- Β© 2025 Mukharbek Organokov
- π Website: www.organokov.com
- π License: GNU General Public License v3.0
βββ README.md # Project overview, setup, deploy, and domain setup
βββ package-lock.json # Exact, locked dependency versions for reproducible installs
βββ package.json # Project metadata + dev scripts (e.g., "dev" with `serve`)
βββ public/ # All files served to the browser (site root)
β βββ assets/ # Images, icons, downloadable files, etc.
β βββ about.html # About page (also reachable at /about via Render rewrites)
β βββ experience.html # Experience/Resume page
β βββ education.html # Education page
β βββ blog.html # Blog landing page (or list of posts)
β βββ contact.html # Contact page (Formspree form lives here)
β βββ index.html # Current homepage (also used as βshellβ for aggregate loader)
β βββ index-old.html # Previous homepage kept for reference (not linked)
β βββ 404.html # Custom not-found page (shown on broken routes)
β βββ robots.txt # Crawler rules; points to sitemap.xml
β βββ sitemap.xml # SEO sitemap of canonical βprettyβ URLs
β βββ src/ # Versioned static assets (cache-busted by path)
β βββ css/ # Stylesheets (site-wide CSS, component styles)
β βββ js/ # JavaScript for UX and dynamic assembly
β βββ aggregate.js # Fetches each page, extracts main sections, and mounts them
β βββ main.js # Global UI (nav, animations) + delegated contact form handler
βββ render.yml # Render Static Site blueprint (routes/rewrites + headers/caching)
βββ serve.json # Local dev config for `npx serve` (headers/spa/caching; optional)
- Static, zero-backend (served from
/public) - Pretty URLs (
/about,/experience,/blogetc.) via Render rewrites - Dynamic βaggregateβ loader that assembles sections into a single page
- Contact form wired to Formspree (no server code by decision)
- Resilient UX: custom
404.html, smooth scrolling, mobile nav - SEO essentials:
sitemap.xml,robots.txt, canonical tags - Security & perf headers via
render.yml(HSTS, caching)
Requirements: Node 18+ (any static server works)
# install deps (if needed)
npm ci
# serve /public locally at http://localhost:3000
npx serve public -l 3000Any static server is fine (python -m http.server, VS Code Live Server, etc.).
The site is entirely static β no build step required.
Used Formspree for simplicity - no server code.
- Obtain you own
https://formspree.io/f/YOUR-KEYfrom Formspree; - Target Email: set in the formβs Settings β Email Notifications;
- (Optional) Project β Settings β Restrict to Domain:
organokov.com(set your domain); - DevTools β Network to verify:
POST https://formspree.io/f/YOUR-KEYreturns200 {"ok":true}; - Check Submissions if emails donβt arrive; also inspect spam/junk.
Form action in public/contact.html:
<form id="contactForm" action="https://formspree.io/f/YOUR-KEY" method="POST">
<!-- name / email / message fields ... -->
</form>
<p id="formStatus" aria-live="polite"></p>Delegated JS handler is bound globally in public/src/js/main.js, so it works even when the form is injected by the section loader:
if (!window.__contactDelegatedBound) {
window.__contactDelegatedBound = true;
document.addEventListener('submit', async (e) => {
const form = e.target;
if (!form.matches('#contactForm')) return;
e.preventDefault();
const statusEl = document.getElementById('formStatus');
if (statusEl) statusEl.textContent = 'Sending...';
try {
const res = await fetch(form.action, {
method: form.method || 'POST',
body: new FormData(form),
headers: { Accept: 'application/json' }
});
const txt = await res.text();
console.log('Formspree status:', res.status, txt);
if (res.ok) {
form.reset();
statusEl && (statusEl.textContent = 'Thanks! Iβll get back to you shortly.');
} else {
let msg = txt; try { msg = JSON.parse(txt)?.errors?.[0]?.message || txt; } catch {}
statusEl && (statusEl.textContent = 'Error: ' + msg);
}
} catch (err) {
console.error(err);
statusEl && (statusEl.textContent = 'Network error. Please try again.');
}
});
}Pages live as separate HTML files:
about.htmlexperience.htmlblog.htmlcontact.html
but are served at clean paths (/about, etc) using Render rewrites (see render.yml).
Use root-relative links in HTML:
<a href="/about">About</a>
<a href="/experience">Experience</a>
<a href="/blog">Blog</a>
<a href="/contact">Contact</a>Deployed with Render using render.yml file.
You can feed your GitHub project directly there (make it public).
Use npm run dev for local development. Be aware, that start is only used if you run a Node server; Render static sites don't need it but itβs handy for testing.
{
"scripts": {
"dev": "npx serve public -l 3000",
"start": "npx serve -s public -l $PORT"
}
}Deployment file render.yml sets security + caching headers:
Security:
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originStrict-Transport-Security: max-age=31536000; includeSubDomains
Caching:
- Short cache for
*.html - Long cache for
/src/**and/assets/**
Custom Domain:
- For example,
www.organokov.comβ CNAME to your Render subdomain organokov.comβ A βYOUR_IP(or ALIAS/ANAME to the Render subdomain)- TLS certificates are issued automatically by Render.
Basic SEO setup, please extend by your wish.
- Each page includes a
<link rel="canonical" β¦>tag (e.g.,/about). public/sitemap.xmllists canonical, pretty URLs.robots.txtpoints to the sitemap.404.htmlshould include<meta name="robots" content="noindex">.