Pure HTML/CSS Landing Page: Hero, Features, Pricing, FAQ (Docs-Style)
You don’t need a framework to publish a credible landing page. You need disciplined HTML, restrained CSS, and the operational paranoia to keep it fast when marketing inevitably adds “just one more badge.”
This is a production-minded blueprint: a docs-style page that reads like engineering documentation, sells like a landing page, and behaves like static content should—predictable, cacheable, and boring in the best way.
- No JavaScript required
- Accessible by default
- SEO meta sanity
- SRE-grade troubleshooting
Table of contents
- Design goals for a docs-style landing page
- HTML structure that scales without drama
- Features section that answers real objections
- Pricing that prevents support tickets
- FAQ that reduces sales noise
- Facts and history: why “just HTML/CSS” keeps winning
- Practical operator tasks: commands, outputs, decisions
- Fast diagnosis playbook
- Common mistakes: symptoms → root cause → fix
- Three corporate mini-stories from the trenches
- Checklists / step-by-step plan
- Next steps
Design goals for a docs-style landing page
A docs-style landing page is what happens when you respect the reader’s time. The goal is not to hypnotize someone with gradients until they click a button. The goal is to let them self-qualify quickly: “Is this for me?” and “Will it work in my environment?”
If you’ve ever been on-call while a marketing experiment shipped a 5 MB hero video, you already understand the premise: reliability is a feature, and a landing page is part of the system. It participates in your SLOs whether anyone admits it in the planning meeting.
Opinionated take: pure HTML/CSS is a default, not a flex. Add JavaScript only when it buys measurable value (conversion, accessibility, or usability), and keep it optional.
What “docs-style” really means
Docs-style isn’t just typography. It’s an information contract. You state what the product does, how it behaves, what it costs, and what it doesn’t do—without making the reader decode marketing metaphors.
Specifically:
- Navigation is predictable: a TOC, anchor links, and stable headings.
- Pricing is explicit: what’s included, what’s not, what happens at limits.
- FAQ is written like support already got the ticket.
- Performance is treated like budgeted infrastructure, not a vibe.
Constraints that make the page better
Pure HTML/CSS gives you constraints that save you from yourself. No build chain, no dependency drift, no “someone upgraded the router and the landing page broke.” Your risk surface shrinks. The deployment story becomes: upload a file, set cache headers, verify.
Constraint is also a design tool. When you can’t rely on a framework component for everything, you write less and choose more carefully: fewer fonts, fewer layouts, fewer breakpoints, fewer miracles.
HTML structure that scales without drama
Landing pages rot when they grow. Someone adds another section, then a sidebar, then a “floating CTA,” and suddenly your headings are out of order and your CSS is an archaeological site.
The fix is not a framework. The fix is structure: semantic HTML, predictable class naming, and a layout that degrades gracefully.
Use semantic landmarks so your page has an operating manual
Use <header>, <nav>, <main>, <section>, and <footer> like you mean it. A docs-style page should be navigable without a mouse and without guessing where the author hid the content.
Headings are an API: don’t break them
Keep one <h1>. Use <h2> for primary sections: Features, Pricing, FAQ, and your operational sections like diagnostics and mistakes. Inside each, <h3> is for subtopics. This matters for accessibility, SEO, and for humans scanning the page at 1.7 seconds per paragraph.
Make the TOC real, not decorative
A table of contents with anchor links does three things: it reduces bounce because people can jump to the question they came for; it makes the page feel “documented”; and it gives you stable targets for internal linking from emails, tickets, and chat.
The trick is stability. Don’t base anchors on content that changes weekly. Use ids that survive copy edits. If you must rename headings, keep ids stable unless you’re ready to support old links.
Pure CSS FAQ: use details/summary intentionally
The details element is the unsung hero of “no-JS” interactivity. It’s keyboard-accessible and works without scripting. Style it lightly. Don’t turn it into a fragile imitation of a JavaScript accordion.
Two rules that prevent 80% of layout bugs
- Set image dimensions (or aspect ratio) so layout doesn’t jump. CLS is an SEO tax and a user trust tax.
- Use
max-widthcontainers. Full-bleed is fine for backgrounds. Text needs a readable measure, not an aircraft runway.
One short joke, because we’ve earned it: CSS is easy until it isn’t, and then it becomes a religious debate with worse tooling.
Features section that answers real objections
Features aren’t a shopping list. They’re rebuttals to objections. In a docs-style page, each feature should explain: what it does, what it replaces, and what failure mode it avoids. If it doesn’t change a decision, it’s decoration.
Static-first delivery with strong caching. HTML can be short-lived; assets can be long-lived and immutable.
Translation: faster global loads and fewer “why is the landing page down?” incidents.
Semantic HTML that works with keyboards and screen readers without a patchwork of ARIA band-aids.
Docs-style navigation is a usability feature, not compliance theater.
Low dependency surface. No build pipeline required. Ship it like config.
Less tooling means fewer supply chain surprises and fewer broken local setups.
Title and description parity, consistent headings, and stable anchors.
Search engines prefer pages that behave like well-organized documents. So do humans.
Truthful claims with limits stated. You avoid expensive “but you didn’t say…” conversations.
Pricing and FAQ become support deflection, which is a polite way to say “less chaos.”
Diagnosable delivery: clear cache policy, clear asset paths, clear observability hooks.
When something breaks, you can find the cause in minutes, not myths.
Feature copy that doesn’t age badly
Avoid versioned claims like “fastest on the market” unless you are prepared to re-prove it weekly. Prefer claims you can keep true with process: “static-first,” “no JavaScript required,” “accessible landmarks,” “predictable caching.”
This is where an ops mindset helps marketing. Promises become constraints. Constraints become reliability.
Pricing that prevents support tickets
Pricing is operational. If it’s vague, you’ll pay for it in pre-sales calls, refunds, angry emails, and engineers being dragged into “quick questions.” A docs-style pricing section should read like a contract preview: what you get, what you don’t, and what happens when you hit limits.
Starter
$0
For internal prototypes and personal pages.
- Single page layout
- Basic TOC + anchors
- FAQ with
details/summary - No custom fonts (ship system fonts)
Team
$29/mo
For product pages that must behave under load.
- Hero + feature grid + pricing + FAQ
- Performance budget checklist
- Cache and CDN guidance
- Accessible navigation patterns
Enterprise
$Custom
For compliance-heavy orgs and boring procurement.
- Content review for legal + accessibility
- Deploy patterns for multi-region
- Incident-ready diagnostics
- Change control templates
Pricing design rules you should adopt
| Rule | What it prevents | How to write it |
|---|---|---|
| State limits explicitly | Surprise overages and “that’s not what we thought” escalations | Use numbers and defaults: page count, build size, support response expectations |
| Make the middle plan real | Enterprise-only funnels that waste everyone’s time | Put the useful plan in the middle; give it enough detail to choose it |
| Clarify what’s excluded | Shadow requirements becoming scope creep | List “not included” items: custom analytics, A/B testing, bespoke design |
| Use stable terminology | Support tickets caused by naming churn | Don’t rename “Team” every quarter; stability is trust |
Second short joke (and that’s the quota): “Unlimited” usually means “unlimited meetings,” and you’ll be billed in attention.
FAQ that reduces sales noise
Your FAQ isn’t a dump of random questions. It’s a shield. It blocks the predictable “what about…” conversations before they become calendar invitations. Keep answers crisp, testable, and written like you’ve met a customer before.
Can a landing page really be “docs-style” and still convert?
Yes, if the reader is technical or time-constrained. Clear structure reduces cognitive load. People click when they trust the page. Trust is built by specific claims and predictable navigation.
Do I need JavaScript for an FAQ accordion?
No. Use details/summary. It’s interactive, accessible, and works in modern browsers. If you need analytics on open/close, add JS later and keep the baseline functional.
How do I avoid layout shift (CLS) without a framework?
Set width/height on images, reserve space for media, and avoid late-loading fonts. Use font-display: swap when you do load fonts, and prefer system fonts when you can.
What’s the simplest deployment strategy?
Serve static files via Nginx (or a CDN). Cache immutable assets for a long time. Cache HTML for a short time. Use atomic deploys so you never serve HTML that references missing assets.
How do I keep anchor links stable if headings change?
Keep the id stable even if the visible heading text changes. Treat anchors like API endpoints: backward compatibility matters because external links will exist.
What’s the right font strategy?
Start with system fonts. Add a custom font only if it materially improves brand perception. If you do add one, host it yourself, subset it, and preload responsibly.
How do I write pricing so support doesn’t hate me?
Define limits, state what happens when you hit them, and list exclusions. If “fair use” is doing heavy lifting, rewrite it until it doesn’t.
Is pure HTML/CSS good for SEO?
It’s often excellent. The content is immediately present, headings are clean, and rendering is fast. SEO problems usually come from missing metadata, duplicate titles, or broken canonical assumptions.
How do I ensure the page stays fast as it evolves?
Set a performance budget (bytes, requests, and largest content element). Review changes like you review infrastructure: measure before/after, and roll back if needed.
Facts and history: why “just HTML/CSS” keeps winning
Some context helps you argue for simplicity in rooms that love complexity. Here are concrete facts that matter when you’re choosing pure HTML/CSS for a landing page.
details/summary exists to provide built-in disclosure widgets. It’s a native way to reveal content progressively without scripting.“Hope is not a strategy.” — General Gordon R. Sullivan
That line belongs on the wall near your “let’s add a third analytics script” request. If you can’t explain the failure mode and the rollback plan, you’re not shipping a feature; you’re shipping a surprise.
Practical operator tasks: commands, outputs, decisions
This is the part most landing pages don’t include: how to run the thing like it matters. Below are practical tasks you can do on a Linux host serving your static HTML, with commands, realistic outputs, and what decision you make based on them.
Assume Nginx is serving /var/www/landing, and you deploy to a host called server. Adjust paths and names to fit your environment, but keep the decision logic.
Task 1: Validate the HTML is actually being served
cr0x@server:~$ curl -I http://127.0.0.1/
HTTP/1.1 200 OK
Server: nginx/1.24.0
Content-Type: text/html
Content-Length: 48231
Last-Modified: Mon, 29 Dec 2025 10:15:44 GMT
ETag: "67713d10-bc67"
Accept-Ranges: bytes
What it means: You’re getting a 200 and the content is HTML. ETag and Last-Modified exist, so conditional requests can work.
Decision: If this is not 200, fix routing/virtual host before chasing performance ghosts. If Content-Type is wrong, your server config or files are wrong.
Task 2: Check cache headers for HTML (short-lived by design)
cr0x@server:~$ curl -I http://127.0.0.1/ | grep -i cache-control
Cache-Control: public, max-age=60
What it means: HTML is cached for 60 seconds. That’s a common compromise: avoids thrash, allows updates to appear quickly.
Decision: If HTML is cached for hours, you’ll ship fixes slowly and confuse users. If it’s not cached at all, your origin will work harder than necessary.
Task 3: Check cache headers for immutable assets (long-lived)
cr0x@server:~$ curl -I http://127.0.0.1/assets/app.3f2c1a9e.css | egrep -i 'cache-control|content-type'
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
What it means: CSS is cached for a year and marked immutable, which is correct only if the filename is content-hashed.
Decision: If you don’t have hashed filenames, don’t use immutable. You’ll create “why doesn’t the styling update?” tickets for yourself.
Task 4: Confirm gzip/brotli is effective (bytes matter)
cr0x@server:~$ curl -I -H 'Accept-Encoding: gzip' http://127.0.0.1/ | egrep -i 'content-encoding|content-length'
Content-Encoding: gzip
Content-Length: 8421
What it means: HTML is compressed. Smaller transfer, faster first render on slow networks.
Decision: If compression is missing, enable it for text types. If it’s enabled but Content-Length stays huge, check for already-compressed content or misconfiguration.
Task 5: Verify that you’re not serving accidental 404s (broken asset paths)
cr0x@server:~$ tail -n 20 /var/log/nginx/access.log
127.0.0.1 - - [29/Dec/2025:10:27:05 +0000] "GET /assets/app.3f2c1a9e.css HTTP/1.1" 200 21844 "-" "curl/8.5.0"
127.0.0.1 - - [29/Dec/2025:10:27:06 +0000] "GET /assets/hero.webp HTTP/1.1" 200 148920 "-" "curl/8.5.0"
127.0.0.1 - - [29/Dec/2025:10:27:07 +0000] "GET /assets/font.woff2 HTTP/1.1" 404 153 "-" "curl/8.5.0"
What it means: A missing font file is being requested. That can cause FOIT/FOUT, layout shifts, and ugly rendering.
Decision: Fix the path or remove the reference. Do not ignore 404s “because the page mostly works.” 404s are performance problems too.
Task 6: Spot slow requests at the origin (Nginx timing)
cr0x@server:~$ awk '{print $NF}' /var/log/nginx/access.log | tail -n 5
0.001
0.002
0.001
0.089
0.001
What it means: One request took 89 ms at Nginx. If that’s common, you have IO, CPU, or upstream slowness.
Decision: If static content is slow to serve locally, check disk latency, filesystem, and whether you’re accidentally proxying dynamic content.
Task 7: Confirm your Nginx config is what you think it is
cr0x@server:~$ nginx -T 2>/dev/null | sed -n '1,40p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
What it means: The loaded configuration is valid and you’re seeing the real running config.
Decision: If you can’t reproduce a header or routing behavior, start here. Many “mystery” issues are “wrong file edited.”
Task 8: Check TLS and certificate validity (don’t wait for the alert)
cr0x@server:~$ echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Dec 10 00:00:00 2025 GMT
notAfter=Mar 10 23:59:59 2026 GMT
What it means: Certificate dates are visible. If you’re close to notAfter, you’re about to have a very public outage.
Decision: Renew early, automate renewals, and monitor expiry. Cert outages are the most avoidable kind of outage, which is why they’re so embarrassing.
Task 9: Check DNS resolution time (latency before you even connect)
cr0x@server:~$ dig example.com +stats | egrep 'Query time|SERVER'
;; Query time: 42 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
What it means: DNS lookup took 42 ms locally. For end users, DNS can be worse. Slow DNS inflates perceived slowness.
Decision: If DNS is slow, investigate resolvers, caching, and record health. Don’t blame CSS for DNS problems.
Task 10: Inspect asset sizes on disk (budget enforcement)
cr0x@server:~$ du -h /var/www/landing/assets | sort -h | tail -n 8
56K /var/www/landing/assets/app.3f2c1a9e.css
148K /var/www/landing/assets/hero.webp
320K /var/www/landing/assets/logo.svg
1.2M /var/www/landing/assets/screenshot.png
What it means: A screenshot PNG is 1.2 MB. That’s a candidate for conversion to WebP/AVIF and resizing.
Decision: Set an asset size budget and enforce it in review. If a single image is over a few hundred KB, it must justify itself.
Task 11: Confirm your HTML references are correct (no missing hashes)
cr0x@server:~$ grep -nE 'assets/.*\.(css|js|png|webp|woff2)' /var/www/landing/index.html | head
34: <link rel="stylesheet" href="/assets/app.3f2c1a9e.css">
66: <img src="/assets/hero.webp" width="1200" height="700" alt="...">
What it means: Your page references hashed assets and images have explicit dimensions. Good.
Decision: If assets are un-hashed and long-cached, fix the pipeline. If images lack dimensions, add them to reduce CLS.
Task 12: Check open file limits and worker health (static still needs OS hygiene)
cr0x@server:~$ cat /proc/$(pgrep -n nginx)/limits | egrep 'Max open files|Max processes'
Max open files 1024 1024 files
Max processes 62967 62967 processes
What it means: Nginx can open only 1024 files per worker process. Under load, this can cause errors or latency.
Decision: If you expect high concurrency, raise limits via systemd unit overrides and Nginx config. Static pages can still get DoS’d by popularity.
Task 13: Validate that your CDN (or reverse proxy) is actually caching
cr0x@server:~$ curl -I https://example.com/ | egrep -i 'age|via|x-cache|cache-control'
cache-control: public, max-age=60
via: 1.1 varnish
age: 38
x-cache: HIT
What it means: The response is being served from cache (HIT) and has an Age of 38 seconds.
Decision: If you always see MISS, you’re paying for a CDN you’re not using. Fix cache keys, cookies, or headers preventing caching.
Task 14: Detect compression effectiveness on assets (don’t compress the uncompressible)
cr0x@server:~$ curl -I -H 'Accept-Encoding: gzip' http://127.0.0.1/assets/hero.webp | egrep -i 'content-encoding|content-type'
Content-Type: image/webp
What it means: No Content-Encoding, which is correct. WebP is already compressed; gzipping it wastes CPU for little gain.
Decision: Compress text; don’t waste cycles on already-compressed images.
Task 15: Confirm log-level and error visibility (don’t fly blind)
cr0x@server:~$ tail -n 20 /var/log/nginx/error.log
2025/12/29 10:27:07 [error] 1402#1402: *91 open() "/var/www/landing/assets/font.woff2" failed (2: No such file or directory), client: 127.0.0.1, server: _, request: "GET /assets/font.woff2 HTTP/1.1", host: "127.0.0.1"
What it means: Clear evidence of what’s missing and where Nginx looked for it.
Decision: Fix the file path and redeploy. Also add a deploy check that fails if referenced assets don’t exist.
Fast diagnosis playbook
If the landing page is slow or broken, you don’t want a brainstorming session. You want a sequence that finds the bottleneck fast. This playbook is ordered to eliminate entire classes of issues quickly.
First: confirm what “slow” means and where
- Is it global or local? Test from the server itself (
curlto localhost) and from a client network. - Is it first byte or render? If TTFB is high, look at origin/proxy. If TTFB is fine but the page “feels” slow, look at payload and rendering (images, fonts).
- Is it consistent or spiky? Spiky implies resource contention, rate limiting, or cache churn.
Second: rule out caching and routing mistakes
- Check for 404s in access/error logs. Broken assets look like “slow” due to retries and fallback behaviors.
- Inspect
Cache-Controland whether the CDN is returning HITs. - Verify you’re serving the correct build (no mixed versions across hosts).
Third: look for the classic performance culprits
- Images: largest asset dominates. Resize, convert, and lazy-load below the fold if appropriate.
- Fonts: late-loading fonts can block text rendering or cause layout shift. Prefer system fonts or properly subset WOFF2.
- Too many requests: each request costs latency. Inline small critical CSS, but don’t inline everything and bloat HTML.
Fourth: verify OS and disk aren’t the hidden bottleneck
- If Nginx is slow to serve local files, check disk latency, file limits, and CPU saturation.
- If TLS handshakes are slow, check cert chain, CPU, and whether HTTP/2 is enabled.
Operational stance: treat your landing page like a mini service. Give it budgets, logs, and a rollback plan. It’s cheaper than explaining outages to executives.
Common mistakes: symptoms → root cause → fix
Most landing page failures are not mysterious. They are recurring patterns with specific symptoms. Here are the ones I see repeatedly when teams ship “simple static content” and then accidentally make it complex.
| Symptom | Root cause | Fix (specific) |
|---|---|---|
| Page “updates” but some users still see old styles | CSS is cached long-term but filename is not content-hashed | Use hashed filenames for assets and Cache-Control: immutable. Or shorten cache if you can’t hash. |
| Layout jumps after load (CLS) | Images missing width/height; late-loading fonts; injected banners | Set image dimensions; reserve space for banners; prefer system fonts or preload/subset WOFF2. |
| Slow first load on mobile | Hero media too large; too many font weights; uncompressed HTML/CSS | Compress text; reduce font variants; convert images to WebP/AVIF; cap hero image bytes. |
| CDN never caches (always MISS) | Cookies vary the cache key; headers prevent caching; HTML marked private | Remove cookies for static paths; set explicit Cache-Control; confirm response headers at the edge. |
| Anchor links break after a rewrite | IDs derived from headings and regenerated; ids renamed casually | Keep stable IDs; add redirects if you must change paths; treat anchors like public API. |
| SEO titles/descriptions look wrong in previews | Title in <title> differs from on-page H1; meta description missing/too long |
Keep H1 aligned with intent; keep description 140–160 characters and stable across deploys. |
| Users report “blank” content briefly | Font loading blocks text (FOIT), aggressive font-display defaults | Use font-display: swap; preload only essential fonts; consider dropping custom fonts. |
| Intermittent 404s after deploy | Non-atomic deploy: HTML references new assets before assets are present on all nodes | Deploy assets first, then HTML; or use atomic release directories and symlink swap. |
| Security scanner flags “mixed content” | Hard-coded http asset links or third-party includes | Use https everywhere; self-host critical assets; consider CSP to enforce. |
Three corporate mini-stories from the trenches
These are anonymized, plausible, and painfully familiar. Each has a lesson you can apply to your own pure HTML/CSS landing page before the pager applies it for you.
Mini-story 1: The incident caused by a wrong assumption
A mid-size SaaS company shipped a new landing page for a product launch. It was “static,” hosted behind a CDN, and everyone assumed that meant it was essentially unbreakable. The team did the usual: a hero, a features grid, and a pricing table. They also added a custom font from a third-party host because “branding.”
Launch morning, traffic arrived. The page loaded slowly for a chunk of users, and a smaller chunk saw broken typography and shifting layout. The conversion funnel looked like someone pulled a plug halfway through the flow. Marketing blamed the CDN. Engineering blamed “mobile devices.” Support blamed everyone.
The wrong assumption was subtle: they assumed the CDN would cache the HTML consistently. But the origin set a cookie for A/B testing on the root path. The CDN respected that cookie and varied the cache key, effectively disabling caching for HTML. Now every request went to origin, and origin had to fetch a remote font on first request to warm connection pools.
As load increased, origin latency climbed. The CDN stopped being an accelerator and became a polite forwarding service. The first obvious metric was TTFB creeping up. The second was error logs filling with timeouts fetching the font host, because third-party dependencies do not scale according to your launch calendar.
The fix was boring: stop setting cookies on the static path, and self-host the font. Once they made HTML cacheable again and removed the third-party font hop, TTFB stabilized and the layout stopped shifting. The page didn’t become “fancy.” It became reliable. That’s what the business needed.
Mini-story 2: The optimization that backfired
Another org, larger and more “process mature,” decided to optimize their landing page by inlining all CSS into the HTML. The argument sounded reasonable: reduce requests, speed up first render. Someone measured a small improvement on a local test and declared victory. They rolled it out to production.
Two weeks later, a designer tweaked button styles. A simple change turned into a deployment incident: caches behaved unpredictably, and users saw a mix of old and new styling depending on which edge node they hit. Because CSS was inline, you couldn’t cache it separately with long TTL. Every HTML change invalidated everything. Their CDN cache hit rate tanked whenever anyone edited a comma.
Worse, HTML responses ballooned. The TTFB was still okay, but time-to-download increased on slow networks because every request carried the full CSS payload. The “optimization” made the page more sensitive to content churn. The moment marketing started experimenting, performance became inconsistent.
They eventually settled on a sane split: a small critical CSS snippet inline (just enough for above-the-fold), plus an external, hashed CSS file for everything else. Now the CDN could cache assets aggressively, while HTML remained a smaller, short-lived object. The landing page became faster and easier to change without turning caching into roulette.
Lesson: not all request reductions are wins. Cacheability is a performance feature. Treat your CDN like an ally, not a dumb pipe.
Mini-story 3: The boring but correct practice that saved the day
A fintech company maintained a set of static pages: marketing, status, and a docs-ish product overview. They deployed via an atomic release directory pattern: upload a new build to a versioned directory, verify locally, then switch a symlink that Nginx serves.
One Friday afternoon, a content update included a renamed asset directory. The HTML referenced /assets/app.9c0a...css, but the deploy script mistakenly uploaded assets into /static/ for that release. If the company had been doing non-atomic, in-place uploads, the page would have served broken references intermittently as files appeared and disappeared mid-deploy.
Instead, the atomic pattern prevented the partial state from ever being live. Their verification step—curl to localhost and grep for 404s in the access log—failed before the symlink swap. The release never went live. No incident. No late-night rollback. Just an engineer muttering, fixing the script, and going home.
That is the kind of “boring correctness” that rarely gets celebrated and frequently saves your weekend. Keep it boring. Boring is scalable.
Checklists / step-by-step plan
This is the plan I’d hand to a team that wants a docs-style landing page in pure HTML/CSS and wants it to stay healthy after it ships. Follow it in order. Don’t “optimize” it until you have data.
Step 1: Content structure before styling
- Write the H1 as the promise you can keep.
- Draft a 1–2 paragraph lead that names a real pain point.
- Create a TOC with stable anchors.
- Write features as objections answered, not adjectives.
- Write pricing like a contract excerpt: inclusions, exclusions, limits.
- Write FAQ from actual tickets and sales calls.
Step 2: CSS that behaves under change
- Use a max-width container and readable line length.
- Use
clamp()for typography to avoid breakpoint soup. - Prefer grid/flex layouts with simple breakpoints.
- Avoid global resets that surprise form elements and accessibility tooling.
- Keep class names descriptive; avoid clever nesting that future you can’t debug.
Step 3: Asset policy (where performance lives)
- Decide: system fonts first; custom fonts only with a written justification.
- Set an image budget per page section; enforce it in review.
- Use WebP/AVIF for large images; keep SVG for logos/illustrations that benefit.
- Hash filenames for cacheable assets.
Step 4: Deployment that prevents partial state
Deploy static pages like you deploy binaries: atomically, and with verification. One clean pattern is release directories + symlink swap.
cr0x@server:~$ sudo mkdir -p /var/www/landing/releases/2025-12-29_1015/assets
cr0x@server:~$ sudo rsync -a ./site/ /var/www/landing/releases/2025-12-29_1015/
cr0x@server:~$ sudo ln -sfn /var/www/landing/releases/2025-12-29_1015 /var/www/landing/current
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
What the output means: Config is valid; reload is safe. The symlink swap is atomic, so users never see a half-deployed state.
Decision: If nginx -t fails, do not reload. Fix config, then retest. If you can’t explain your deploy’s failure modes, you’re deploying hope.
Step 5: Verification gates (simple but strict)
- Run
curl -Iand verify caching headers. - Fetch the HTML and grep for expected asset references.
- Scan logs for 404s immediately after deploy.
- Check TLS expiry dates if you changed domains or certificates.
Step 6: Operational ownership
Assign an owner for the page’s performance budget and deploy process. “Marketing owns it” is how you get 12 trackers and a hero video with autoplay. “Engineering owns it” is how you get a page that never ships. The right answer is shared: marketing owns content, engineering owns delivery constraints.
Next steps
If you want a docs-style landing page that works in production, do three things this week:
- Freeze the structure: semantic sections, stable anchors, and an FAQ that answers real questions.
- Set budgets: image sizes, number of fonts, and caching rules for HTML vs assets.
- Make deploys atomic: release directories and symlink swaps, plus a post-deploy check for 404s and headers.
Pure HTML/CSS isn’t nostalgia. It’s a decision to ship less risk. You can always add complexity later. Removing it is the part that hurts.