| Owner | Hisham Ali |
| Status | v1.1 — open decisions resolved, ready for implementation |
| Last updated | 2026-05-09 |
| Target audience for this doc | Implementing developer (Hisham, or any senior full-stack engineer he hands this to) |
| Estimated build effort | 2–3 focused weekends |
A personal portfolio website for Hisham Ali, a full-stack developer based in Lund, Sweden, targeting junior to mid-level developer roles in the Swedish tech market. The site is the primary online presence backing the candidate's CV, GitHub profile, and LinkedIn — a focused, professionally executed showcase of recent work, technical depth, and trajectory.
The site is intentionally small in surface area but built to a high quality bar. It functions both as content (work samples, narrative, contact) and as evidence (the codebase itself is a sample of the developer's work and is expected to be inspected by recruiters and engineering managers).
Visual direction is minimal monochrome: no accent colour, restrained motion, typography-led hierarchy, generous whitespace.
- Establish a credible, professional online presence at a custom domain.
- Showcase three flagship projects (NordicMatch, SpeedMate, Resume LaTeX UI) with real technical depth.
- Communicate a distinctive narrative (multidisciplinary background, AI-augmented engineering, trilingual professional).
- Provide an accessible, performant, well-coded reference implementation that itself demonstrates engineering craft.
- Drive recruiters and hiring managers to a clear, low-friction contact path.
- A blogging platform with a high publication cadence.
- A CMS or admin UI.
- E-commerce, authentication, user accounts, or social features.
- A design portfolio in the visual-design sense (no heavy imagery, animation showcases, or interactive art).
- A general-purpose marketing site.
| Persona | Need | What they look for |
|---|---|---|
| Swedish recruiter (technical) | Quickly verify candidate substance | Stack alignment, recent work, location, language, contactability |
| Engineering hiring manager | Assess engineering judgement and craft | Real projects with depth, code quality of the site itself, written communication |
| Fellow engineer / peer reviewer | Evaluate technical credibility | Architecture choices, repo hygiene, willingness to discuss trade-offs |
| Future self | Maintainable artefact | Clean structure, low operational burden, easy to refresh |
Primary audience is the first two. All design and content decisions optimise for their first 30–90 second scan.
Quantitative
- Lighthouse score ≥ 95 across Performance, Accessibility, Best Practices, SEO on both home and a representative case study page.
- Largest Contentful Paint < 1.5s on a simulated 4G connection.
- First-load JavaScript per route < 100 KB gzipped.
- Total transferred bytes on the home page < 500 KB.
- 100% AA WCAG 2.2 conformance on automated and manual checks.
Qualitative
- Site renders correctly on iOS Safari, Android Chrome, and the latest two versions of Chrome, Firefox, Safari and Edge on desktop.
- A recruiter can find email, LinkedIn, and GitHub within 5 seconds of landing.
- Each case study answers four questions: what was the problem, what did I build, what were the trade-offs, what was the outcome.
In scope
- Single statically rendered website deployed to Vercel.
- One language: English.
- Five page types: Home, About, Work index, Work case study (parameterised), Contact.
- Three published case studies: NordicMatch, SpeedMate, Resume LaTeX UI.
- Privacy-friendly analytics.
- Custom domain, HTTPS, dynamic OG image, sitemap,
robots.txt. - Light theme only.
Explicitly out of scope for v1.0 (deferred to roadmap, see §20)
- Swedish translation.
- Dark mode.
- Blog / writing section.
- Contact form with backend.
- RSS feed.
- Comments.
- Authentication.
/
├── /about
├── /work
│ ├── /work/nordicmatch
│ ├── /work/speedmate
│ └── /work/resume-latex-ui
├── /contact
└── /404
A single horizontal navigation, persistent on every page: Home · About · Work · Contact. Sticky on scroll on the home page only; static on inner pages. The wordmark on the left links home.
Minimal: copyright year, "Built with Next.js, hosted on Vercel" attribution, plus three small links (email, GitHub, LinkedIn). No newsletter, no social grid, no sitemap link.
A compact, doorway-style page.
Sections, in order:
- Hero block (above the fold) — stacked layout: large name on two lines ("Hisham" / "Ali"), then role line, then value proposition, then status badge, then action pair. Each element on its own row with vertical rhythm; no side-by-side splitting.
- Name as primary heading: Hisham Ali.
- Role line: "Full-Stack Developer · Lund, Sweden".
- One-sentence value proposition (≤ 25 words).
- Status badge: "Open to junior / mid-level roles".
- Two primary actions:
View work →(anchor to/work) andGet in touch →(anchor to/contact).
- Selected work — three cards, one per featured case study. Each card: project name, one-line description, key tech tags rendered as plain inline text (not pill chips), link to the case study.
- Brief intro paragraph (2–3 sentences) with a
Read more →link to/about. - Footer.
No carousels, no autoplaying media, no marquee text.
Long-form prose page. First-person, conversational, professional. No marketing voice.
Structure:
- Page heading.
- Section 1 — Where I am now: current focus, stack, what I'm building.
- Section 2 — How I got here: the narrative arc — Damascus → systems analyst at SSRC → relocation → Komvux → Teknikhögskolan → developer. Framed as evidence of resilience and learning capacity, not as biography.
- Section 3 — How I work: AI-augmented development, formal CS fundamentals, systems thinking inherited from the SSRC role.
- Section 4 — Outside work: photography, languages, travel.
- End-of-page facts block: languages with CEFR levels, location & availability, current focus, selected coursework / distinctions.
Length target: 600–900 words.
A simple list view of all case studies. Each entry shows:
- Project name (heading).
- One-paragraph description (40–60 words).
- Stack as inline text.
- Year.
- "Read case study →" link.
Sorted reverse-chronological. No filtering or tagging in v1.
Authored as MDX (see §10.4 and Appendix A). Each case study follows a consistent template with the following sections:
- Title.
- One-line summary.
- Metadata strip: role, year, stack, links (live, repo).
- Context — what was the problem, who was it for.
- Approach — what I built and why, with architectural choices called out.
- Trade-offs — what I considered and rejected.
- Outcome — what shipped, what I learned, numbers where possible.
- Optional: architecture diagram (inline monochrome SVG).
No screenshot-only case studies. Every case study must be readable as standalone writing.
Minimal page. No form in v1.
- Page heading.
- Short paragraph indicating availability and preferred channels.
- Email (
mailto:), LinkedIn, GitHub as plain text links with the small external-link arrow. - Location and timezone.
Plain page. Heading, one-line message ("That page doesn't exist."), link back to home. Same chrome as the rest of the site.
- Restraint over decoration. Every element earns its place; when in doubt, remove it.
- Typography carries the design. Hierarchy, rhythm and contrast come from type scale and weight, not from colour or ornament.
- Generous whitespace. Vertical rhythm and breathing room are non-negotiable.
- Content-first. Layout serves the writing; the writing is the product.
- Consistent monochrome. No accent colours, no gradients, no shadows beyond the subtlest elevation cues.
| Token | Hex | Use |
|---|---|---|
--background |
#FAFAFA |
Page background |
--surface |
#FFFFFF |
Elevated surfaces (cards, callouts) |
--foreground |
#0A0A0A |
Primary text |
--muted-foreground |
#52525B |
Secondary text, metadata |
--subtle-foreground |
#A1A1AA |
Tertiary text, captions |
--border |
#E4E4E7 |
Hairline dividers, card borders |
--border-strong |
#D4D4D8 |
Emphasised dividers, diagram strokes |
--focus-ring |
#0A0A0A |
Keyboard focus indicator |
--selection-bg |
#0A0A0A |
Text selection background |
--selection-fg |
#FAFAFA |
Text selection foreground |
No accent colour. Links are styled with the foreground colour and an underline that appears or thickens on hover. The selection highlight is the only place where the page inverts, and that is deliberate.
All tokens declared as CSS custom properties in styles/tokens.css and consumed via Tailwind's theme.extend.colors mapping.
All typefaces self-hosted via next/font/google for zero layout shift and optimal performance.
| Role | Family | Weights | Notes |
|---|---|---|---|
| Display & body | Geist Sans | 400, 500, 600 | Primary face for everything |
| Mono | Geist Mono | 400, 500 | Dates, code, small metadata |
Geist Sans alone carries the entire design — display, body, UI, navigation. No editorial serif accent. This is a deliberate restraint choice consistent with the minimal-monochrome direction; one family for prose and UI, one mono face for metadata, nothing else.
Type scale (rem, base 16px):
| Token | Size | Line height | Weight | Use |
|---|---|---|---|---|
text-display |
4.5rem (72px) | 1.05 | 500 | Hero name (mobile: 3rem) |
text-h1 |
3rem (48px) | 1.1 | 500 | Page titles (mobile: 2.25rem) |
text-h2 |
2rem (32px) | 1.2 | 500 | Section headings |
text-h3 |
1.5rem (24px) | 1.3 | 500 | Subsection headings |
text-h4 |
1.25rem (20px) | 1.4 | 500 | Card titles |
text-body |
1.0625rem (17px) | 1.65 | 400 | Body prose |
text-small |
0.9375rem (15px) | 1.55 | 400 | Secondary copy |
text-meta |
0.8125rem (13px) | 1.5 | 500 | Metadata strips (mono) |
Letter-spacing: -0.02em on display sizes, -0.01em on h1–h2, default elsewhere. Maximum line length for body prose: 68 characters (use max-w-[68ch]).
Spacing scale, in pixels (multiples of 4): 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128, 160. Implemented through Tailwind's default scale.
Layout:
- Maximum content width: 720px for prose pages (About, case studies), 960px for the work index, 640px for the contact page.
- Page horizontal padding: 24px on mobile, 48px on tablet, scales with viewport on desktop.
- Vertical rhythm: 96px between major page sections on desktop, 64px on mobile.
No formal grid system. Use CSS flexbox and grid pragmatically; centred single-column for prose.
Icons from lucide-react only. Sized 16px or 20px, stroke width 1.5. Used very sparingly: external-link indicators, navigation hints, contact link prefixes. Never decoratively.
The site contains no decorative photography in v1. Imagery is permitted only as:
- Architecture diagrams (inline SVG, monochrome, hairline strokes matching
--border-strong). - A single optional headshot on the About page (square crop, max 240px, desaturated to grayscale).
- The Open Graph social card (generated dynamically by
app/og/route.tsx).
Restrained and purposeful.
- All transitions: 150ms,
cubic-bezier(0.4, 0, 0.2, 1). - Hover states only on interactive elements (links, buttons).
- Page transitions: none — default browser behaviour.
- One subtle entrance animation acceptable: hero text fade-in over 300ms on initial load.
- Respect
prefers-reduced-motion: reduce— disable all non-essential motion.
No parallax, no scroll-jacking, no autoplay video, no loading spinners (the site loads instantly).
A small set of components is sufficient. Built on shadcn/ui where useful; most are bespoke and small.
| Component | Notes |
|---|---|
SiteHeader |
Wordmark + primary nav |
SiteFooter |
Persistent across pages |
Hero |
Home page hero block |
WorkCard |
Used on home (selected work) and work index |
Prose |
Wraps About and case study content with consistent typography |
MetadataStrip |
Top of case study: role, year, stack, links inline |
ExternalLink |
Adds rel="noopener noreferrer", target="_blank", small arrow icon |
MDXComponents |
Custom renderers for h2, h3, p, ul, code, pre, blockquote |
shadcn/ui components to install: button, separator. That is the entire list. Anything more is over-engineering.
| Layer | Choice | Version |
|---|---|---|
| Framework | Next.js (App Router) | 15.x |
| UI library | React | 19.x |
| Language | TypeScript | 5.x, strict: true |
| Styling | Tailwind CSS | 4.x (or 3.4 if 4 has blockers in target environment) |
| Component primitives | shadcn/ui | latest |
| Content authoring | MDX | via @next/mdx for Next 15 |
| Frontmatter | gray-matter + Zod schema |
latest |
| Icons | lucide-react |
latest |
| Fonts | Geist Sans, Geist Mono | via next/font/google |
| Analytics | Vercel Analytics | latest |
| Linting | ESLint flat config + eslint-config-next |
latest |
| Formatting | Prettier + prettier-plugin-tailwindcss |
latest |
| Tests | Vitest (unit), Playwright (smoke) | latest |
| Package manager | pnpm | latest |
| Node | 20 LTS minimum (22 LTS preferred) | — |
All pages are statically rendered at build time (export const dynamic = 'force-static'). No runtime data fetching, no API routes in v1 except the OG image route which is statically cacheable. Revalidation is unnecessary — redeploy to update.
portfolio/
├── app/
│ ├── (site)/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Home
│ │ ├── about/
│ │ │ └── page.tsx
│ │ ├── work/
│ │ │ ├── page.tsx # Index
│ │ │ └── [slug]/
│ │ │ └── page.tsx # Case study
│ │ ├── contact/
│ │ │ └── page.tsx
│ │ └── not-found.tsx
│ ├── og/
│ │ └── route.tsx # Dynamic OG image
│ ├── globals.css
│ ├── robots.ts
│ ├── sitemap.ts
│ └── layout.tsx # Root layout (font, metadata, JSON-LD)
├── components/
│ ├── ui/ # shadcn primitives
│ ├── site-header.tsx
│ ├── site-footer.tsx
│ ├── hero.tsx
│ ├── work-card.tsx
│ ├── prose.tsx
│ ├── metadata-strip.tsx
│ ├── external-link.tsx
│ └── mdx-components.tsx
├── content/
│ └── work/
│ ├── nordicmatch.en.mdx
│ ├── speedmate.en.mdx
│ └── resume-latex-ui.en.mdx
├── lib/
│ ├── content.ts # MDX loader + frontmatter Zod schema
│ ├── metadata.ts # Reusable Next Metadata helpers
│ ├── i18n.ts # Locale constants and helpers (forward-compatible)
│ └── seo.ts # JSON-LD generators
├── messages/
│ └── en.json # UI string catalogue (see §11)
├── public/
│ ├── favicon.ico
│ ├── icon.svg
│ ├── apple-icon.png
│ └── og-default.png # Static fallback OG image
├── styles/
│ └── tokens.css # CSS custom properties
├── .github/
│ └── workflows/
│ └── ci.yml
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── eslint.config.mjs
├── prettier.config.mjs
├── package.json
├── pnpm-lock.yaml
└── README.md
Case studies live in content/work/{slug}.en.mdx. The .en segment is mandatory in v1 to keep the loader forward-compatible with future locales (see §11).
Each file has YAML frontmatter:
---
title: 'NordicMatch'
slug: 'nordicmatch'
summary: 'A full-stack matching platform built on Next.js 15 with split deployment across Vercel and Fly.io.'
year: 2026
role: 'Solo developer'
stack:
[
'Next.js 15',
'TypeScript',
'PostgreSQL',
'Drizzle ORM',
'BullMQ',
'Redis',
'Gotenberg',
]
links:
live: 'https://example.com' # optional
repo: 'https://github.com/...' # optional
status: 'published' # or "draft"
order: 1
---Frontmatter is parsed with gray-matter and validated against a Zod schema in lib/content.ts. The loader enumerates the directory, filters status: "published", sorts by order then year, and exposes typed accessors used by the work index and case study pages.
generateStaticParams() returns the slugs at build time so every published case study is pre-rendered.
generateMetadata()per page (title, description, canonical URL, OG image, Twitter card).- Title format:
{Page} — Hisham Ali(the home page is justHisham Ali — Full-Stack Developer). - Default OG image rendered via
next/ogfromapp/og/route.tsx: name + role on the brand background, statically cached. - Per-case-study OG images use the same template with the case study title.
- JSON-LD:
Personschema in the root layout;CreativeWorkper case study. app/sitemap.tsenumerates static routes plus published case studies.app/robots.tsallows everything except/api/*— the rule is forward-compatible even though no API routes exist in v1.
Vercel Analytics. Zero-config, free on the Hobby tier, GDPR-compliant without a cookie consent prompt, no extra account to manage. Install @vercel/analytics and mount the <Analytics /> component in the root layout. No custom event tracking in v1.
No forms in v1. Contact is via mailto: links. If a contact form is added later (§20), use a Next.js Server Action plus Resend or Postmark; do not introduce a client-side form library before it is needed.
The site ships in English only. The codebase is structured so that adding Swedish later is a localised, low-risk change rather than a refactor.
Day-1 commitments to enable this:
- All user-facing UI copy lives in a translation catalogue at
messages/en.json, not hardcoded in JSX. Even though only English exists, every visible string is referenced via at('home.hero.title')-style key. A small typed helper inlib/i18n.tsexposest(key)reading from the JSON file at build time. This keeps the pattern in place without taking the runtime cost of a full i18n library. - No locale-coupled formatting in components. Dates, numbers and lists are formatted via
Intl.DateTimeFormat,Intl.NumberFormatandIntl.ListFormatwith the active locale ('en') passed in explicitly — never with the runtime default. - MDX content uses a locale-aware filename convention from day one:
content/work/{slug}.{locale}.mdx. v1 shipscontent/work/{slug}.en.mdx; the loader resolves.en.mdxby default. - Routing is left flat (
/about,/work) in v1 to avoid an unnecessary/en/...prefix while only one locale exists. The middleware-driven prefixing is added in v1.1. <html lang>is set from a constant, ready to switch from'en'to a dynamic value.
These five commitments are the entire i18n investment in v1. They do not require installing next-intl or any other library yet.
When Swedish is added:
- Adopt
next-intl(App Router native, type-safe, server-component friendly). Install, wire upi18n.tsandmiddleware.ts. - Move pages into
app/[locale]/.... Addlocaleto all generated metadata and OG images. - Translation catalogues:
messages/en.jsonandmessages/sv.json. Every key inen.jsonmust exist insv.json— enforce in CI. - Default locale:
en. Locale negotiation order: URL prefix → first-party cookie →Accept-Languageheader → fallback to default. Persist user choice in a first-party cookie. - Locale switcher in the site header. The active locale is the one in the URL.
- MDX case studies: each case study has paired
.en.mdxand.sv.mdxfiles. If a Swedish file is missing, fall back to English with a small inline notice. sitemap.tsenumerates both locales and emitshreflangalternates.- Translation done by Hisham personally; no machine translation in production copy.
This work is scoped to roughly one weekend of effort given the day-1 preparation above.
| Metric | Target | Measurement |
|---|---|---|
| Lighthouse Performance | ≥ 95 | PageSpeed Insights (mobile + desktop) |
| Lighthouse Accessibility | ≥ 95 | PageSpeed Insights |
| Lighthouse Best Practices | ≥ 95 | PageSpeed Insights |
| Lighthouse SEO | 100 | PageSpeed Insights |
| LCP | < 1.5s (4G) | Vercel Speed Insights or WebPageTest |
| INP | < 200ms | Real-user data via Plausible / Vercel |
| CLS | < 0.05 | Lighthouse |
| Page weight, home (gzipped) | < 500 KB | Bundle analyser |
| First-load JS, any route | < 100 KB gzipped | next build output |
Practical constraints to hit these:
- All routes statically rendered.
- All fonts self-hosted via
next/fontwithdisplay: 'swap'. - All images via
next/imagewith explicit width/height;priorityonly on the LCP element. - Default to React Server Components; promote to client components only when interactivity demands it.
- No global state libraries.
- No client-side analytics SDK beyond a single small script.
- WCAG 2.2 AA conformance. Verified by
axe-core(Playwright integration) and manual screen reader testing (VoiceOver on macOS, NVDA on Windows). - Semantic HTML: one
<h1>per page, headings in document order,<nav>,<main>,<footer>landmarks. - Skip-to-content link, visible on keyboard focus.
- Visible focus indicators: 2px solid
--focus-ring, 2px offset. - Colour contrast ≥ 4.5:1 for body text, ≥ 3:1 for large text and UI components.
langattribute on<html>(constant"en"in v1, dynamic in v1.1).- All interactive elements reachable and operable by keyboard.
prefers-reduced-motion: reducerespected: disables hero entrance animation and all transitions beyond instant state changes.- All meaningful images have
alttext; decorative SVG diagrams userole="img"andaria-label. - No forms in v1; mailto links and external links are clearly labelled.
Tier 1 (full support, tested every release): Latest two major versions of Chrome, Firefox, Safari, Edge desktop. iOS Safari 17+. Current Android Chrome.
Tier 2 (best-effort, no visual regression bug fixes unless trivial): Older Safari/Chrome within the last 24 months.
Tier 3 (unsupported, may degrade): Internet Explorer (any), legacy Edge.
Viewport range: 320px to 2560px. Tested at 320, 375, 768, 1024, 1440, 2560.
- HTTPS enforced (Vercel default).
- Strict security headers via
next.config.tsheaders():Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Content-Security-Policy: default-src 'self'; script-src 'self' va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' vitals.vercel-insights.com; frame-ancestors 'none'.
- No cookies set in v1 (analytics is cookieless).
- No personal data collected via the site beyond what visitors voluntarily send via email.
- A short privacy note linked from the footer is sufficient; no consent banner required.
- Target an A or A+ rating on
securityheaders.com.
- pnpm for package management.
- Node 20 LTS minimum, pinned via
.nvmrcandenginesinpackage.json. - VS Code as the assumed editor;
.vscode/settings.jsonincludes format-on-save.
| Script | Command |
|---|---|
dev |
next dev |
build |
next build |
start |
next start |
lint |
eslint . && tsc --noEmit |
format |
prettier --write . |
format:check |
prettier --check . |
test |
vitest run |
test:e2e |
playwright test |
analyze |
ANALYZE=true next build |
- ESLint:
eslint-config-nextplus@typescript-eslintrecommended rules. - Prettier with
prettier-plugin-tailwindcssfor class-name sorting. - TypeScript:
strict,noUncheckedIndexedAccess,noImplicitOverride. - Conventional commits, loosely. Not strict tooling — discipline. Use
feat:,fix:,docs:,chore:,refactor:prefixes. This helps the history read cleanly to a reviewer skimminggit log. - Pre-commit:
lint-stagedruns Prettier and ESLint on staged files.
.github/workflows/ci.yml runs on every push and PR:
- pnpm install (cached).
pnpm lint.pnpm format:check.pnpm build.pnpm test.- Optional: Lighthouse CI against the Vercel preview URL.
main is always deployable and auto-deploys to production on Vercel. Feature work happens on branches → PR → preview deploy → review → squash merge.
Get the scaffolding right before the first feature commit. The single biggest mistake in portfolio repos is "I'll add ESLint/Prettier/CI later" — later never comes, and the early commits look amateur. Bake all of it in before any feature code ships.
The first three commits should be infrastructure only:
chore: initial Next.js scaffold—create-next-appoutput with TypeScript strict, ESLint flat config, Prettier with the Tailwind plugin,.editorconfig,.nvmrc, pnpm lockfile, comprehensive.gitignore, and a basic README. Nothing else.chore: add design tokens— the CSS custom properties from §8.2 instyles/tokens.css, mapped through Tailwind'stheme.extend.chore: add CI— the GitHub Actions workflow from §16.4 (lint, format check, typecheck, build).
Only then start writing components, page by page. Each commit small, each one buildable.
| Item | Choice |
|---|---|
| Host | Vercel (Hobby tier is sufficient) |
| Domain | hishamali.dev (registered) |
| DNS | Registrar-managed CNAME / ALIAS to cname.vercel-dns.com |
| TLS | Vercel-managed (Let's Encrypt) |
| Environments | Production (main), Preview (per-PR), Local (pnpm dev) |
| Required env vars | None in v1 (Vercel Analytics is zero-config) |
| Region | Default (Vercel global edge) |
Connect Vercel to the repo as soon as the first commit lands on main. Preview deploys for every PR from day one are invaluable for sanity-checking visual changes against real rendering. Point the custom domain at Vercel after a handful of commits — even a scaffold-only page is fine to be live; nobody is looking yet, and getting DNS/HTTPS settled early avoids a launch-day scramble.
The following original copy must be written before launch. Total: roughly 1,500 words.
| Surface | Word count | Source |
|---|---|---|
| Hero value proposition | 1 sentence (≤ 25 words) | Original |
| Home intro paragraph | 60–80 words | Adapted from CV summary |
| About page | 600–900 words | Original, drafted from CV narrative |
| NordicMatch case study | 600–800 words | Original |
| SpeedMate case study | 500–700 words | Original (Combain LIA 2 work) |
| Resume LaTeX UI case study | 400–600 words | Original |
| Contact page intro | 30–50 words | Original |
| Footer attribution | 1 line | Original |
| OG image text | Name + role | Static |
Each case study must include either an inline architecture diagram or an annotated code excerpt — never just prose.
The site is ready to launch when all of the following are true:
- All five page types implemented and rendering correctly.
- Three case studies authored, reviewed, and published.
- Lighthouse ≥ 95 across all four categories on Home and at least one case study.
- Manual screen reader pass on Home, About, and one case study.
- Tested on iOS Safari, Android Chrome, latest desktop Chrome and Safari.
- All security headers verified via
securityheaders.com(target A or A+). - Sitemap and
robots.txtvalid and submitted to Google Search Console. - OG image renders correctly in LinkedIn, Slack, X, iMessage previews.
- No console errors or warnings in any tested browser.
- CI green: lint, format check, build, unit tests, smoke tests.
- Custom domain configured and HTTPS verified.
- Analytics receiving events from production.
- README documents setup, scripts, content authoring, and deployment.
Everything described above.
Adopt next-intl, introduce [locale] segment, translate UI strings and three case studies, locale switcher, hreflang alternates, per-locale OG images.
Add next-themes and a theme toggle. Mirror all colour tokens for a dark theme. Persist preference, respect prefers-color-scheme.
Add a /notes index for short essays (AI-augmented engineering, Next.js patterns, systems thinking). Same MDX authoring pattern as case studies. RSS feed at /rss.xml.
Server Action + Resend, honeypot, rate-limit, optional Cloudflare Turnstile if abuse appears.
Live "now" page, an interactive case study (e.g., embedded NordicMatch demo), a small /uses page.
| # | Question | Status |
|---|---|---|
| 1 | ✅ Resolved — hishamali.dev registered |
|
| 2 | ✅ Resolved — Vercel Analytics | |
| 3 | Headshot on About page — yes or no | Open — decide before About page implementation |
| 4 | ✅ Resolved — omitted; Geist Sans only | |
| 5 | NordicMatch case study — how much can be disclosed pre-launch? | Open — decide before case study draft |
---
title: 'Project Name'
slug: 'project-name'
summary: 'One-line summary, 15–25 words.'
year: 2026
role: 'Solo developer'
stack: ['Next.js 15', 'TypeScript', '...']
links:
live: 'https://...' # optional
repo: 'https://github.com/...' # optional
status: 'published'
order: 1
---
## Context
What problem was this solving? Who was it for? What constraints shaped it?
## Approach
What I built and why. Call out one or two architectural decisions explicitly:
why this stack, why this boundary, why this trade-off.
## Trade-offs
What I considered and rejected. What I would do differently next time.
## Outcome
What shipped. What I learned. Numbers where possible.
<!-- Optional inline architecture diagram (SVG) -->- brittanychiang.com — canonical developer portfolio reference
- leerob.com — idiomatic Next.js / Vercel execution
- linear.app — restraint, monochrome execution, type discipline
- vercel.com — typography and motion sensibility
- pangrampangram.com — typographic confidence
End of document.