Case study — personal portfolio

The website you're looking at,
explained from the inside.

Every technical decision, every SEO optimization, every architectural choice — documented in detail. Not to show off, but because this is exactly the method I use on every client project.

ClientMarco Bagiacchi (self)
Year2025 → 2026
TypePortfolio + Brand
Duration6 weeks
● PageSpeed Insights · Desktop
99
Performance
100
Accessibility
100
Best Practices
100
SEO
● PageSpeed Insights · Mobile
91
Perf.
100
Access.
100
Best P.
100
SEO

Starting from scratch,
on purpose.

The old website ran on WordPress with a commercial theme. It worked, but it was heavy: every page loaded unused plugins, unnecessary JS and bloated CSS from a builder designed for hundreds of different layouts, not mine.

The decision was clear-cut: rewrite everything from scratch in vanilla HTML/PHP, with no framework, no CMS, no compromises. A website that weighs exactly what it needs to — not a byte more.

The main constraint: Hostinger shared hosting. No Node.js, no build pipeline. Everything had to run on native PHP and Apache — a constraint that turned out to be a virtue: zero dependencies, maximum control.

A portfolio website doesn't have to impress the person who built it. It has to impress the person who sees it — and do so in under 3 seconds.

Project
marcobagiacchi.it
Type
Personal portfolio + digital brand identity
Goal
Win organic clients, rank on Google in Perugia
Stack
PHP 8 HTML5 Vanilla CSS Vanilla JS Apache .htaccess Hostinger
Analytics
GTM GA4 CookieYes GCM v2

What the website
had to do.

Before touching the code, I defined every requirement. A freelancer's portfolio website isn't just a showcase — it's a conversion tool working around the clock.

Perceived loading speed ≤ 1 s

First Contentful Paint under 1 second on standard connections. Visitors must see content before deciding whether to stay.

Organic ranking for "sviluppatore web Perugia" (web developer Perugia)

The main keyword, with commercial intent. Technical SEO has to be perfect: H1/H2 structure, JSON-LD, sitemap, canonical.

PageSpeed 100 on Accessibility, Best Practices, SEO

No compromise on accessibility: WCAG AA contrast, correct ARIA, clean semantic HTML.

GDPR-compliant analytics with Consent Mode v2

Google Consent Mode v2 active before any script. CookieYes handles the banner. GA4 via GTM — zero double-tracking.

A memorable design, not a generic one

A dark theme with cyan/purple accents. Mixed serif/mono typography. An animated hero, interactive elements. A website people remember.

Security and correct HTTP headers

HSTS, X-Frame-Options, a base CSP, directory listing blocked, sensitive files protected (.env, .git). All via .htaccess.

The problems to solve
before writing a line.

Every project has its own knots to untie. This one had five, and each had implications for the architecture of the whole solution.

01

Header and footer duplicated across 24 pages

With pure HTML, every menu change required 24 manual updates. One slip and the website would be inconsistent. It needed a component system — without JavaScript, without a build step.

02

Clean URLs without a CMS

The files are .php, but the URLs must be /siti-web — not /siti-web.php. Handling redirects, custom 404s and canonicals without conflicts on Hostinger's Apache.

03

GDPR analytics: order matters

Google Consent Mode v2 must run before GTM, which must run before GA4. CookieYes must update consents after the banner. The wrong order invalidates the data or breaches the GDPR.

04

Performance vs third parties

CookieYes and GTM bring in third-party JavaScript that can't be compressed. Reaching 99 on Performance with these scripts active means compensating on everything else: minified CSS, font preload, CSS-only animations.

05

SEO without losing the design

Creative titles ("Your website shouldn't just exist") don't contain keywords. The solution: an H1 with the keyword (visually small, monospace), an H2 with the creative title (large, serif). Google reads the H1, the user remembers the H2.

The chosen architecture,
with all the whys.

SOLUTION 01

A PHP component system

Instead of a framework or a CMS, I used native PHP include. Three shared files — header.php, footer.php, analytics.php — included in every page via $_SERVER['DOCUMENT_ROOT'].

Result: I edit the menu in one place, and the change propagates to all 24 pages. Zero JavaScript required, zero external dependencies, 100% compatible with Hostinger.

// Every page includes the components:
<?php include $_SERVER['DOCUMENT_ROOT']
    . '/components/header.php'; ?>

// header.php contains nav, mobile menu,
// GTM noscript — one file to update

<?php include $_SERVER['DOCUMENT_ROOT']
    . '/components/footer.php'; ?>

Why not a JS fetch? A component loaded via fetch isn't visible to SEO crawlers at initial crawl time — an indexing risk.

SOLUTION 02

Clean URLs via .htaccess

Four Apache rules in sequence cover every case: force HTTPS, remove www, strip the .php extension from URLs, and handle the special case of /progetti/ (a real directory + a PHP file with the same name — without the dedicated rule it causes an infinite redirect).

The canonical 301 on every URL ensures Google always and only indexes the correct version: https://marcobagiacchi.it/siti-web, never with .php or www.

# 1. Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# 2. Remove .php from the URL
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
RewriteRule ^ /%1 [R=301,L]

# 3. Special fix /progetti/ → /progetti
RewriteRule ^progetti/$ /progetti [R=301,L]
RewriteRule ^progetti$ progetti.php [L]

# 4. Serve .php for clean URLs
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^\.]+)$ $1.php [L]
SOLUTION 03

A GDPR-compliant analytics stack

Loading order isn't negotiable. A shared PHP file — analytics.php — included in every <head> always guarantees the right sequence.

CookieYes loaded with async: it removes render blocking (−900ms), while the wait_for_update: 2000 in GCM v2 ensures GTM waits for consent before firing events.

// 1. GCM v2 — MUST run before everything
gtag("consent", "default", {
  analytics_storage: "denied",
  wait_for_update: 2000, // wait for CookieYes
});

// 2. CookieYes — async, doesn't block rendering
<script id="cookieyes" async
  src="cdn-cookieyes.com/..."></script>

// 3. GTM — loads GA4, waits for consent
// GA4 is NOT included directly:
// avoids double-tracking
SOLUTION 04

An H1/H2 hierarchy for both SEO and design

Creative titles don't contain keywords — but they're memorable. Keywords aren't memorable — but Google loves them. The solution: use both, together.

H1 = keyword in eyebrow style (monospace, 12px, uppercase) — visible but discreet.
H2 = creative title (display serif, large) — the one the user remembers.
H3 = section titles with secondary keywords.

No hidden text, no gray-hat techniques. Google reads the H1 and climbs the rankings, the user reads the H2 and decides to stay.

<!-- H1: keyword for Google, visually discreet -->
<h1 style="font-family:var(--mono);
         font-size:12px;text-transform:uppercase">
  Web developer Perugia
</h1>

<!-- H2: creative title for the user -->
<h2 class="h-display">
  Your website <u>shouldn't</u> just
  exist.
  It should be <em>remembered.</em>
</h2>

Result: SEO 100/100 on PageSpeed, structure validated by Google Search Console.

Every tool chosen
for a precise reason.

The rule is simple: no dependency without a measurable advantage. Every library added is a security risk, a performance cost, a version to update. For a static website like this one, vanilla is the most professional choice.

Frontend
Semantic HTML5
Vanilla CSS + variables
Vanilla JavaScript ES6
Space Grotesk (sans)
JetBrains Mono
Instrument Serif Italic
Inline SVG (0 icon fonts)
Backend / Server
PHP 8 (native include)
Apache mod_rewrite
.htaccess (URL, cache, gzip)
Hostinger shared hosting
Analytics & Compliance
Google Consent Mode v2
CookieYes (GDPR banner)
Google Tag Manager
GA4 (via GTM)
SEO
JSON-LD Schema.org
sitemap.xml (25 URLs)
robots.txt
Absolute canonical URLs
Open Graph + Twitter Card
Google Search Console
Why I did NOT use
WordPress
React / Next.js
Bootstrap / Tailwind
jQuery

How I worked,
week by week.

6 weeks of non-linear development: design and code moved forward in parallel, in short iterative cycles. No 40-page spec document — just quick decisions, continuous testing and immediate refactoring.

Week 1
Strategy & architecture

Defining primary and secondary keywords for each page. Site map, URL hierarchy, PHP component structure. Not a pixel yet.

↳ Keyword research · sitemap · wireframe
Weeks 2–3
Design system & homepage

CSS variables, typography, palette. Animated hero, reusable components (buttons, cards, sections). All 8 service pages.

↳ Design system · 14 pages · mega menu nav
Week 4
Technical SEO & content

Optimizing titles, descriptions and H1/H2/H3 across all 24 pages. JSON-LD for each page type. Sitemap.xml, robots.txt, absolute canonicals.

↳ 24 pages optimized · sitemap · JSON-LD
Week 5
Performance & analytics

Integrating GCM v2 + CookieYes + GTM in the right order. PageSpeed optimization: font preload, minified CSS (−22%), HTTP preload header, CSS keyframes for mobile LCP.

↳ Performance 86 → 99 desktop · Accessibility 90 → 100
Week 6
Deploy, testing & case study

Deploy on Hostinger, HTTPS + HSTS checks. Cross-browser, mobile and accessibility testing. Sitemap submitted to Search Console. Writing the case study.

↳ Live · Search Console · rich results with 0 critical errors

From 86 to 99 on Desktop.
Every point, earned.

Performance isn't a number to show off — it's the time your visitors don't waste waiting. Every optimization took analysis, testing and sometimes the courage to step back when the chosen solution made things worse.

PageSpeed Insights screenshot: Performance 99, Accessibility 100, Best Practices 100, SEO 100

PageSpeed Insights · Desktop · marcobagiacchi.it

The 6 optimizations that made the difference

01
Font preload — breaks the CSS → font chain
Three <link rel="preload" as="font"> tags in the <head> start downloading the woff2 files in parallel with the CSS, removing the CSS→font dependency from the critical chain.
−500ms LCP
02
Minified CSS — from 114 KB to 89 KB (−22%)
Minification via a custom Python script: comments and whitespace removed, selectors optimized. Faster for the browser to download and parse.
−25 KB
03
CSS keyframes for the hero animation — no JS for LCP
The hero title started at opacity:0 and waited for the JS IntersectionObserver. On mobile with a 4× CPU throttle, this delayed LCP by 800–1500ms. Solution: pure CSS @keyframes, no dependency on JS.
−1.4s mobile LCP
04
HTTP preload header via .htaccess
The CSS is announced in the HTTP response header, before the browser even reads the HTML. The browser starts downloading it in parallel with HTML parsing, eliminating the round trip.
−359ms CSS chain
05
Correct JavaScript caching — from 7 days to 1 year
Hostinger served script.js with a 7-day TTL for the text/javascript MIME type. The .htaccess rule only covered application/javascript. Both MIME types added.
Faster repeat visits
06
Forced reflow fixed in the reviews carousel
offsetWidth was re-read on every click/scroll, forcing the browser to recalculate the layout. Solution: cache the value, invalidated only on resize.
−69ms TBT

Why mobile Performance is 91 and not 100

The 9 missing points on mobile don't depend on the website — they depend on CookieYes and Google Tag Manager, third-party scripts required for GDPR and analytics. They can't be modified: they live on external servers, with legacy code and fixed bundles.

PageSpeed error: Legacy JavaScript from the CookieYes CDN

Legacy JS — CookieYes bundle (34 KiB, not modifiable)

PageSpeed error: unused JavaScript from Google Tag Manager

Unused JS — GTM bundle (61 KiB, not modifiable)

These same errors appear on any website using GTM + a CMP (Consent Management Platform). They aren't a problem with the website — they're the necessary cost of GDPR-compliant analytics. The 99/100 realistically achievable with these scripts has already been reached on desktop.

The final numbers,
unfiltered.

99
Desktop Performance
PageSpeed Insights
91
Mobile Performance
With GTM + CookieYes active
100
Accessibility
WCAG AA · desktop + mobile
100
Best Practices
HTTPS, headers, CSP
100
SEO Score
Canonical, JSON-LD, sitemap
0
Rich Results errors
Google Search Console
0.8s
Desktop LCP
Largest Contentful Paint
24
Pages optimized
SEO + performance + a11y

What I learned
building this website.

→

Vanilla beats any framework for static websites. No dependencies to update, no bundle to optimize, no abstraction to learn. The final CSS is 89 KB — a React "Hello World" app weighs more.

→

The CSS preload trick without inline critical CSS is a trap. The first implementation crashed performance from 86 to 56. The FOUC (flash of unstyled content) hurts LCP and CLS more than the preload helps them.

→

JS animations block LCP on mobile. IntersectionObserver under a 4× CPU throttle delays the reveal of the hero element by 800–1500ms. Solution: pure CSS keyframes that start as soon as the browser has parsed the stylesheet.

→

Google doesn't clearly document the order of analytics scripts. GCM v2 → CookieYes async → GTM. Swapping even one step invalidates the data or breaches the GDPR. The wait_for_update: 2000 is the glue that holds it all together.

→

Contrast isn't an aesthetic option. Changing --ink-3 to --ink-2 on four elements took Accessibility from 90 to 96. Adding cache busters did the rest.

→

A well-built website is the best possible portfolio. You don't need to show what you can do — just show it. Every client who lands on this website already sees the answer to the question "how do you work?"

A note for clients

This is the method I use on every project

The architecture you see here — PHP components, structured technical SEO, GDPR-compliant analytics, optimized performance — isn't reserved for my website. It's the starting point for every project I take on.

What changes from project to project? The content, the design, the specific integrations. The underlying technical quality stays the same.

Performance optimized from the start
Correct technical SEO on every page
GDPR-compliant analytics included
WCAG AA accessibility guaranteed
HTTP security headers configured

Want a website built with the same method?

Tell me what you want to create. I'll show you how I'd build it — no frills, no surprises.