Launch checklist

Before you announce the site: robots.txt and sitemap.xml generated by Next.js, the heaviest files trimmed, and everything the previous pages set up verified from the outside — redirects, 404, certificate, headers, IPv6, HTTP/3, performance — with one script that prints PASS or FAIL.

beginner~30 min hands-on
#launch#seo#robots#sitemap#tls#hsts#performance#nextjs

Not validated end to end yet — be the first.Report a problem

Draft — not yet run end to end. This page was written but its author has not yet run it on a real machine. Commands may be wrong: read before you run, and tell us what breaks.

The gistThe site, seen from the outside
Your VPS at OVH
openscancurl · opensslcrawl
Your laptopcurl · dig · openssl
Public testersSSL Labs · securityheaders · Lighthouse
https://${DOMAIN}Caddy · static files
Search enginesGoogle · Bing

Your laptop and a few public testers check https://${DOMAIN} the way a visitor reaches it: redirects, certificate, headers, IPv6, speed. Search engines read robots.txt and sitemap.xml from the same server.

The site is live. Before you tell anyone, this page adds the two files a static export does not produce by itself — robots.txt and sitemap.xml — trims the files that make pages slow, then checks from the outside, the way a visitor or a crawler sees it, everything pages 4 to 7 set up. It ends with the search engines and a short list for announcement day.

Before you start

The site is deployed with the script from page 6 and answers on its domain. Everything on this page runs on your Mac, from the project folder, except the HSTS edits on the server. Line up two friends as external testers for the end: one on mobile data, one on another operator's Wi-Fi. First, the home page answers:

Check
$curl -s -o /dev/null -w '%{http_code}' https:///
Expected output
200

Add robots.txt and sitemap.xml

output: "export" writes only what the app declares. Next.js has two metadata files for this: app/robots.ts and app/sitemap.ts. At build time each becomes a plain file in out/.

Set the indexing choice in the panel first. Leave it off while the site still holds examples or features that only pretend to work, such as a contact form that simulates sending: crawlers would index them as they are. Switch it on, and redeploy, on launch day.

In a terminal on your Mac, go to the project folder, then copy each file block below and paste it in the terminal: each one writes its file.

Mac
$cd
Macwrites a fileapp/robots.ts
import type { MetadataRoute } from "next";
export const dynamic = "force-static";
const SITE = "https://";
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: "*", allow: "/" },
sitemap: SITE + "/sitemap.xml",
};
}
Macwrites a fileapp/sitemap.ts
import type { MetadataRoute } from "next";
export const dynamic = "force-static";
const SITE = "https://";
const ROUTES = [
"/", "/bio/", "/bookmarks/", "/chess/", "/clippings/", "/contact/",
"/endeavor/", "/how-i-want-to-live/", "/journey/", "/lately/", "/library/",
"/own/", "/pearl/", "/picture/", "/support/", "/thought/", "/travel/", "/writing/",
];
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date();
return ROUTES.map((path) => ({ url: SITE + path, lastModified: now }));
}

Deploy with the script from page 6, which builds before uploading, then check both files were generated:

Mac
$cd
$./scripts/deploy.sh
$ls out/robots.txt out/sitemap.xml
Check
$curl -s https:///robots.txt | head -1
Expected output
User-Agent: *
If the build fails or out/robots.txt is missing

A public/robots.txt or public/sitemap.xml conflicts with the generated one: delete the one in public/. An error naming dynamic or revalidate on /robots.txt means the force-static line is missing. A project laid out in src/app/ takes both files there.

Trim the heaviest files

A 50 MB WAV on a page is a 50 MB download on a phone. List everything over 5 MB that the site serves:

Mac
$cd
$find out -type f -size +5M -exec ls -lh {} \; | sort -k5 -h

Audio: convert each WAV to Opus, or MP3 for the oldest browsers, then change the reference in the code.

Mac
$brew install ffmpeg
$ffmpeg -i public/audio/track.wav -c:a libopus -b:a 128k public/audio/track.opus
$ffmpeg -i public/audio/track.wav -c:a libmp3lame -q:a 2 public/audio/track.mp3
$grep -rn '\.wav' app components lib 2>/dev/null

Images: nothing on a web page needs more than 2400 px on its long side.

Mac
$cd
$cp -R public ../public-before-resize
$find public -iname '*.jp*g' -size +1M -exec sips -Z 2400 {} \;
$du -sh ../public-before-resize public

Check from the outside

Redirects and status codes

Mac
$curl -sI http:/// | grep -iE '^(HTTP|location)'
$curl -sI https://www./ | grep -iE '^(HTTP|location)'
$curl -s -o /dev/null -w '%{http_code}\n' https:///no-such-page/
Expected output
HTTP/1.1 308 Permanent Redirect
Location: https:///
HTTP/2 301
location: https:///
404
Check
$curl -s -o /dev/null -w '%{http_code}' https:///no-such-page/
Expected output
404

TLS

Open SSL Labs' test for your domain. It takes about two minutes. Aim for A; A+ needs an HSTS max-age of at least six months, which the step after these checks sets.

Mac
$open "https://www.ssllabs.com/ssltest/analyze.html?d="
$curl -sv -o /dev/null https:/// 2>&1 | grep -E 'issuer|expire date'
Check
$echo | openssl s_client -connect :443 -servername  2>/dev/null | openssl x509 -noout -issuer | grep -o 'Let.s Encrypt'
Expected output
Let's Encrypt

Security headers

Mac
$open "https://securityheaders.com/?q=https:///&followRedirects=on"
$curl -sI https:/// | grep -iE '^(strict-transport|x-content-type|x-frame|referrer|permissions|content-security)'

Expect strict-transport-security, x-content-type-options: nosniff, referrer-policy and whatever else page 5 set. A missing content-security-policy is expected at this stage: it costs a grade, not security you already had.

IPv6 and HTTP/3

Mac
$dig +short AAAA
$curl -6 -sI https:/// | head -1
$curl -sI https:/// | grep -i '^alt-svc'

Mail spoofing

If you told page 4 the domain sends no mail, check that the "no mail" records are published, so nobody can send phishing in your name:

Mac
$dig +short TXT _dmarc.
$dig +short TXT | grep spf1

Performance

In Chrome, open the site in a private window, then DevTools → Lighthouse → Mobile → Analyze page load. Or use PageSpeed Insights, which runs the same test from Google's servers:

Mac
$open "https://pagespeed.web.dev/report?url=https:///"

Raise HSTS to a year

Page 5 started the HSTS header at one day. Once every check above passes, raise it to a year, on the server:

Mac
$ssh vps
Server·
$sudo sed -i 's/max-age=86400"/max-age=31536000"/' /etc/caddy/Caddyfile
$sudo -u caddy caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy
Check
$curl -sI https:/// | grep -i '^strict-transport-security'
Expected output
strict-transport-security: max-age=31536000

Search engines

Tell Google and Bing the site exists and where the sitemap is.

  1. In Google Search Console, add a property of type Domain with . Google shows a TXT record starting with google-site-verification=.
  2. In the Infomaniak Manager, Zone DNS of the domain (as on page 4), add that TXT record on the domain itself, then click Verify in Search Console. It can take a few minutes.
  3. In Search Console → Sitemaps, enter sitemap.xml and submit.
  4. In Bing Webmaster Tools, sign in and choose the import from Google Search Console: it copies the site and the sitemap.
Mac
$dig +short TXT | grep google-site-verification

Announce

A last look before you post the link and ask your external testers to open the site on their phones:

  • Favicon: /favicon.ico answers 200 (the command below). Next.js serves app/favicon.ico or app/icon.png.
  • Link preview image: a 1200 × 630 JPEG as app/opengraph-image.jpg (Next.js adds the meta tags) and metadataBase set to https:// in the root layout, so the image URL is absolute. Export it from the originals in photos-26-og/; they stay out of the site. Paste the link in a message to yourself to see the preview.
  • 404 page: styled, with a way back home.
  • Stubs: the contact form and chess save have no backend yet; hide them or label them "coming soon".
  • Monitoring: the uptime monitor from page 7 is green and its alerts reach .
  • After launch: the RSS feeds for /lately from the project's TODO can come later; a feed added next week works the same.
Mac
$curl -s -o /dev/null -w '%{http_code}\n' https:///favicon.ico
$curl -s https:/// | grep -o '<meta property="og:image"[^>]*>'

Done

The series is complete: a Next.js site built on your Mac, served over HTTPS by Caddy from your own Debian VPS at OVH, on a domain managed at Infomaniak, deployed atomically, backed up, monitored, and checked from the outside, with Google and Bing told where the sitemap is. The Run script of this page doubles as a smoke test after any big change.

A later page, not written yet — page 9 — will give the contact form and the chess saves a backend on the same VPS: a small Node service behind Caddy, with SQLite.

Did everything work?

If you followed this page to the end on a real machine, say so. Your validation is dated and records your stack, so the next reader on the same path knows it still works.

This copy is read-only. To report that it works, or that it does not, open an issue

Only your stack choices are recorded, never your values. The pseudonym stays on this browser.