Website Launch Checklist 2025
Favicons, link previews, compression, responsive images, WCAG 2.2, dark mode, valid HTML and fonts: what you almost certainly forgot before going live.
Right, so you think you finally finished your website. You think you can go live. Maybe you have the source code, the Wordpress instance all ready to go, the backup ready to deploy to your server.
And hopefully you gave up FTP đ¤Ś.
Because the cool kids around the block use containerization: they deploy images with their code all ready to go. One click, boom. Done.
But you cannot go live yet.
In fact, nobody ever can.
If you want to launch your website, you might as well do it right. So let me go through what a 2025 website should carry, starting with what you almost certainly forgot.
Favicons
Yep, that pesky little icon in your browserâs tab list. That one. Easy to miss, glancing over it, thinking a single .png upload will do.
Actually, not at all.
Different operating systems want different sizes, and some people pin your site to a home screen. A bigger .png saves nobody. The resulting HTML looks something like this:
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
Wow, so many sizes đ˛. Which explains why you reach for a generator instead of doing it by hand. I still come back to RealFaviconGenerator. Upload 1 .svg, click generate, paste the markup into your <head>.
One and done â . Just never change your logo.
One more, new enough that most generators skip it. An SVG favicon can answer the browserâs colour scheme by itself, with a media query inside the SVG. 1 file instead of a light one and a dark one:
<style>
@media (prefers-color-scheme: dark) { path { fill: #fff; } }
</style>
Link Previews
Somebody will paste your URL into Slack, or iMessage, or LinkedIn. What shows up?
If you answered âthe URLâ, you forgot your Open Graph tags:
<meta property="og:title" content="The page's title">
<meta property="og:description" content="One sentence about it.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com/page">
<meta property="og:image" content="https://example.com/preview.png">
<meta property="og:image:alt" content="What the preview picture shows">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
It does seem like a lot. Most modern CMSes handle it for you. Wordpress does it when you set a Feature Image.
3 mistakes I see constantly:
- A relative
og:image. It needs the scheme and the host. A crawler never visits your page, so it has nothing to resolve/preview.pngagainst. - The wrong type on an article. A blog post wants
og:type: article, witharticle:published_time,article:modified_timeandarticle:author. Leave it aswebsiteand you threw away the date and the byline. - Passing an object where a string goes. If your framework hands you an image object and you interpolate it into the tag, you get no error. You get
[object Object]. And you will never notice, because nobody ever looks at the tag.
Paste your own URL into Slack before you launch. Every time.
Compression
Turn it on. That covers the whole section. But check it, because âmy host does itâ and âmy host does itâ mean 2 different things.
curl -sI -H "Accept-Encoding: br, gzip" https://example.com | grep -i content-encoding
Nothing comes back? You ship uncompressed HTML, CSS and javascript to every visitor. Brotli beats gzip on text by roughly 15% to 20% and every serious host supports it. Images and video arrive compressed already, so leave those alone.
Responsive Images
The single biggest thing you can do for someone on a phone on a train.
<img
src="/photo-800.jpg"
srcset="/photo-400.jpg 400w, /photo-800.jpg 800w, /photo-1600.jpg 1600w"
sizes="(min-width: 60rem) 55vw, 100vw"
width="1600"
height="900"
alt="What the photograph shows"
loading="lazy"
decoding="async">
Every attribute there does a job:
srcsetoffers the browser the widths you actually generated.sizestells it how wide the image will render on the page. Get this wrong and it downloads the biggest one every time. Everybody forgets this one.widthandheightreserve the space before the file lands, which stops the page jumping around under someoneâs thumb. Set them even when your CSS overrides them.loading="lazy"on anything below the fold. Never on the picture at the top, which you want fetched immediately.altdescribing the picture, not repeating the heading beside it. An emptyalt=""answers correctly for decoration, and nobody should call that a cop-out.
Writing all of that by hand every time invites mistakes, so let the framework do it. Astro generates the whole thing from the widths you name:
---
import {Picture} from "astro:assets";
import getImage from "@utils/images";
/** The widths a picture spanning the viewport gets generated at. */
const FULL_BLEED_WIDTHS = [640, 1024, 1600, 2400];
const source = getImage(photo.image);
---
<Picture
src={source}
alt={photo.caption ?? ""}
formats={["avif", "webp"]}
widths={FULL_BLEED_WIDTHS}
sizes="(min-width: 60rem) 55vw, 100vw"
loading="lazy"
/>
WCAG 2.2
This can include things like:
- Contrast: can they actually see the text? Did you get fancy and overlay text on an image? If you did, did you check it against the brightest pixel behind it, at every screen size? Checking against your pageâs background colour tells you nothing with a photograph in the way.
- Font sizes: can they read it in a moving car?
- Visible focus:
:focus-visiblestates. When someone tabs through your page, can they see where they landed? alttext: not just for<img>and<picture>but for<video>and<audio>too. Do you have transcripts? People on the go need those as much as people with impairments do.- Target size: 24 by 24 CSS pixels for anything other than a link inside a sentence. New in 2.2, and it catches nearly everybody.
A lot to take in. So how do you keep track?
- Lighthouse for a report you can hand to somebody.
- axe from Deque, whose browser extension finds far more than you expect. Work the errors 1 by 1. Give yourself a few solid days.
- Firefoxâs accessibility inspector, the best of the built-in ones.
Then check the 2 things no tool can. Tab through the whole page. Turn a screen reader on.
Dark Mode
You probably already know about prefers-color-scheme. And you owe nobody a dark mode; you can set color-scheme: light and move on.
But what about someone reading your website outside on a bright day?
Bet you never thought of that. When I visited Tunis, basically everyone wore sunglasses. I tried looking at my phone at full brightness and could not see a thing.
Now imagine that on a website with only a light mode.
Meet WCAG 2.2 already and you will probably get by unscathed. But if your site runs at all fancy, with small text and images under text, take it outside. Can you read it? If not, add a dark mode.
The easy way in 2025 means never writing 2 of everything. light-dark() holds both values in 1 declaration:
:root {
color-scheme: dark light;
--surface: light-dark(#efe9dd, #0b0b0d);
--ink: light-dark(#1c1a16, #f4f3f0);
}
1 declaration per colour, no second block to forget about. Which describes the actual failure mode of the 2-block approach: a token added to one and missed in the other throws no error. It quietly falls back to the wrong scheme and still renders.
Lesson: use custom properties. Declare color literally everywhere and you only made more work for yourself.
Valid HTML
Did you remember to run your site through the W3C validator? If not, do it right now. You might find your HTML conforms to no spec at all.
- More than 1
<main>? Technically you can. Screen readers and Google get confused. Have 1. - Duplicate
idattributes? Anaria-labelledbypointing at the second one silently reads the first. - Headings skipping a level, like an
<h2>followed by an<h4>. Screen reader users navigate by heading, and a gap reads as a missing section. - Interactive elements nested in each other. A
<button>inside an<a>breaks the spec and does nothing you expect. -
langon<html>. 1 attribute. Without it a screen reader might read your English page in a French voice.
Preload And Prefetch
People mix these up constantly.
preloadmeans âI need this for this page, fetch it now, high priority.â The font your headline uses. The hero image. Use it sparingly, since preloading everything achieves the same as preloading nothing.prefetchmeans âthey will probably go here next, grab it when you sit idle.â The next page in an obvious flow.
<link rel="preload" href="/fonts/display.woff2" as="font" type="font/woff2" crossorigin>
You cannot skip that crossorigin on a font preload. Leave it off and the browser fetches the file twice, once for the preload and once for the actual use, because the 2 requests go out in different modes.
Critical-Path CSS
The browser paints nothing until it parses every stylesheet in the <head>. Which explains render-blocking, and why a 200KB stylesheet hurts more than a 200KB image.
3 options, in the order I would try them:
- Ship less CSS. Astro and Tailwind both strip what you never used. Look at what you actually deploy first. You might have no problem to solve.
- Inline what the first screen needs, load the rest asynchronously. Real gains, real maintenance cost, since the inlined copy goes stale the moment somebody edits a component.
- Split by route so a page loads only its own styles.
Measure before doing any of it. Critical CSS on a site shipping 12KB of stylesheet solves a problem you never had.
Font Loading
- Do your fonts load from a CDN? Load them from your own domain instead. Faster even on HTTP/3. And no Google Fonts, which hands a third party a log of your visitors.
-
font-display: swap? Look at your performance tree for that pesky FOIT and FOUT. You will probably just see it. - Fallback stack? Without one you get a flash of unstyled text.
- Tried
size-adjustandascent-overrideon the fallback so both fonts take up the same space? That stops the page reflowing when the real one lands. - Loading 4 weights when the design uses 2? Every one costs a separate file.
Or do what this site does: use the system font stack, ship nothing, and get your character from scale, weight and spacing.
And Then
Open your site on a phone you never built it on, on a network you do not control, and read it.
That catches what the other 12 miss.