Dev Blog

BLOG

Adding a status page with Better Stack and linking it to the app's contact form

How status.metronome.fm runs on Better Stack: the monitors, a custom domain that took a single Cloudflare CNAME, and a contact button that opens the form inside the app.

Published October 7, 2026

Metronome.fm now has a status page that shows whether the service is up: status.metronome.fm. It runs on Better Stack. It went live on the same day I opened the issue for it. The only changes in the app were a component that opens the contact form and a link from the docs.

This post covers what is monitored, how the custom domain points to Better Stack, and how the status page links back to the contact form in the app.

I use Better Stack on the Free plan. Everything described here, including the monitors, the custom domain, and the Japanese version of the page, works within the free tier.

The Better Stack billing page. Number 1 marks the line You're currently on the Free plan.
Billing page
  1. Still on the Free plan

I wanted one after seeing status pages for other services. I was already sending error logs to Better Stack, so I kept monitoring and the status page in the same place.

What is monitored

There are two Better Stack monitors (Uptime monitors). Both send a GET request to a URL and decide whether it is up from the HTTP status of the response.

URLIntervalTimeoutRegions
https://metronome.fm/app3 min30 sEurope, US, Asia, Australia
https://metronome.fm/ja3 min30 sSame

/app is the app itself and /ja is the Japanese landing page. Both follow redirects and verify the SSL certificate.

On the monitor settings page you choose when to be alerted (Alert us when) and which URL to monitor. On the Free plan you can alert when the URL becomes unavailable, when it does or doesn't contain a keyword, when it returns an unexpected status, or when the host doesn't respond to ping. TCP, DNS, Playwright scenarios and others require a paid plan.

Better Stack monitor settings. Number 1 marks URL becomes unavailable under Alert us when, number 2 marks https://metronome.fm/app under URL to monitor.
Monitor settings
  1. Alert when the URL becomes unavailable
  2. The URL to monitor

It checks every 3 minutes, opens an incident as soon as it sees a failure (Confirmation period set to Immediate start), and marks it resolved after 3 minutes of being up (Recovery period).

Advanced monitor settings. Number 1 marks 3 minutes under Check frequency, number 2 marks Immediate start under Confirmation period.
Check interval and how long to wait before opening an incident
  1. Check every 3 minutes
  2. Open an incident right away when a check fails

Alerts for expiring SSL certificates or domains are not available on the Free plan.

Both monitors are shown on the status page, so an outage of either the app or the landing page is visible to users.

Current status by service on the status page. Number 1 marks the metronome.fm/app row and number 2 the metronome.fm/ja row, each with a 90-day uptime bar.
Current status by service
  1. The app (/app)
  2. The Japanese landing page (/ja)

Pointing status.metronome.fm at Better Stack

When I opened the issue, I wondered whether Next.js could serve a subdomain like status.metronome.fm. It turned out I didn't touch Next.js or the app at all. Better Stack serves the status page, and all I had to do was point the name at it with DNS.

The DNS for metronome.fm is on Cloudflare. I added one CNAME record for status that points to Better Stack's status page host.

$ dig +short status.metronome.fm CNAME
statuspage.betteruptime.com.

Because dig shows the CNAME target directly, the record is not proxied through Cloudflare (DNS only).

The status page settings show the CNAME record to add, and once it resolves correctly they show "The CNAME is configured correctly". If your DNS is on Cloudflare, you can also sign in to Cloudflare from this screen and add the record there.

Custom domain settings for the status page. Number 1 marks the CNAME row from status.metronome.fm to statuspage.betteruptime.com, number 2 marks The CNAME is configured correctly.
Custom domain settings
  1. Add this CNAME to your DNS (Cloudflare)
  2. Shown once the record resolves correctly

I added it with that button. After you sign in to Cloudflare and approve, the CNAME record is created.

Opening the app's contact form from the status page

A Better Stack status page can show a contact button in the top right. You set where it goes with the "Get in touch URL" setting; leave it empty to hide the button. mailto: links work too.

Top right of the status page. Number 1 marks the contact button.
Top right of the status page
  1. The contact button. It opens the form in the app

Metronome.fm takes inquiries through a form (a modal) inside the app. The form has no URL of its own, so I made /app open it when ?contact=open is in the URL. The status page's Get in touch URL is set to https://metronome.fm/app?contact=open.

Status page settings. Number 1 marks https://metronome.fm/app?contact=open under Get in touch URL.
Status page settings
  1. Where the contact button goes (Get in touch URL)
export const ContactLinkOpener = () => {
  const searchParams = useSearchParams();
  const router = useRouter();
  const pathname = usePathname();
  const openContactFormModal = useModalState(
    (state) => state.openContactFormModal,
  );
  const shouldOpen = searchParams.get("contact") === "open";

  const open = useEffectEvent(() => {
    const rest = new URLSearchParams(searchParams);
    rest.delete("contact");
    const query = rest.toString();
    router.replace(query ? `${pathname}?${query}` : pathname);
    openContactFormModal();
  });

  useEffect(() => {
    if (shouldOpen) open();
  }, [shouldOpen]);

  return null;
};

Once the form opens, contact=open is removed from the URL. Otherwise the form would open again on reload, or after closing it and navigating within the app. Other query parameters are kept.

The contact form open on top of the Metronome.fm app.
Right after opening /app?contact=open. The form opens on top of the app

The component lives in the header. In Next.js, a component that uses useSearchParams makes everything up to the nearest Suspense boundary render on the client. To keep the whole header from doing that, only this component is wrapped in Suspense.

The Japanese version and links from the site

The status page serves English at / and Japanese at /ja. Visitors can also switch with the language picker at the bottom.

Translations require the newer status page design (Modern look).

Status page settings. Number 1 marks the note that translations are a Modern look feature, number 2 marks Modern look under Status page design.
Status page design settings
  1. Translations are a feature of the newer design (Modern look)
  2. Set the design to Modern look

Better Stack has ready-made translations, but only for English, German, French, Spanish, Dutch, Czech, Swedish and Turkish, not Japanese. So I created a translation with the locale ja and filled in 256 strings in Japanese, covering navigation, status labels, automatic incident reports and more. Claude wrote the translations. The company name can be inserted with placeholders like %{company_name}.

Translation settings. Number 1 marks ja under Locale, number 2 marks None under Ready-made language, number 3 marks the Japanese text in the Status field.
Settings for the Japanese translation
  1. The locale is ja
  2. No ready-made Japanese, so None
  3. Each string is filled in by hand in Japanese
Bottom of the status page. Number 1 marks the language picker.
Bottom of the status page
  1. The language picker

On the Metronome.fm side, the status page is linked from the top of the docs and from the v2.5 release notes. The link follows the current language: / for English and /ja for Japanese.

export const getStatusPageUrl = (locale: string) =>
  locale === DEFAULT_LOCALE
    ? `${STATUS_PAGE_URL}/`
    : `${STATUS_PAGE_URL}/${locale}`;