Adding a Simple Analytics badge to a Next.js site

by Dan Edwards, 16 September 2024

Adding a Simple Analytics badge to a Next.js site

Simple Analytics(opens in a new tab) is an excellent alternative to Google Analytics. It doesn't collect creepy personalised data on your site visitors and doesn't require a cookie consent form. It's beautiful if you care about making a site that's useful and enjoyable to use.

You might think this would mean the data lacks detail and utility, but this isn't true. You can see individual page views and the visitor's country and browser - perfect for a blog. It's also free for multiple sites, as long as you display the Simple Analytics badge on your site, which is a link to your dashboard.

Allow external images

To display images from an external Content Delivery Network (CDN) in our Next.js site, we must configure the image domains in our Next.js configuration. Add a remotePattern to your next.config.mjs:

next.config.mjs
MJS
1/** @type {import('next').NextConfig} */
2const nextConfig = {
3	images: {
4		remotePatterns: [
5			{
6				protocol: 'https',
7				hostname: 'simpleanalyticsbadges.com',
8				port: '',
9				pathname: '/**',
10			},
11		],
12	},
13};
14
15export default nextConfig;

Create components

Next, we need to create Script and Badge components. I've included this code for clarity, but I wouldn't recommend doing it this way. I only create separate components in a React/Next site if I plan to reuse them - otherwise, it's just clutter.

SimpleAnalyticsBadge component

I've updated the badge to use the Next.js Link and Image components. You don't need to get any code from the Simple Analytics platform - just fill out the bareDomain variable with the name of your site. I have added

  • alt for accessibility
  • loading="lazy" as you'll probably have this below the fold in the footer
  • height and width to avoid layout shift
  • unoptimised Without this, Next.js will look for the image at https://my-site.com/https://https://simpleanalyticsbadges.com/my-site.com.
SimpleAnalyticsBadge.tsx
TSX
1import Image from 'next/image';
2import Link from 'next/link';
3
4const bareDomain = 'my-site.com';
5
6export default function SimpleAnalyticsBadge() {
7	return (
8		<Link
9			href={`https://dashboard.simpleanalytics.com/${bareDomain}?utm_source=${bareDomain}&utm_content=badge`}
10			referrerPolicy="origin"
11			target="_blank"
12		>
13			<picture>
14				<source
15					srcSet={`https://simpleanalyticsbadges.com/${bareDomain}?mode=dark`}
16					media="(prefers-color-scheme: dark)"
17				/>
18				<Image
19					src={`https://simpleanalyticsbadges.com/${bareDomain}?mode=light`}
20					alt="Simple analytics"
21					loading="lazy"
22					referrerPolicy="no-referrer"
23					crossOrigin="anonymous"
24					width="201"
25					height="50"
26					unoptimised
27				/>
28			</picture>
29		</Link>
30	);
31}

SimpleAnalyticsScript component

Now for the script. This code is straightforward and the same code for everyone.

SimpleAnalyticsScript.tsx
TSX
1import Script from 'next/script';
2
3export default function SimpleAnalyticsScript() {
4	return (
5		<>
6					<Script
7						async
8						defer
9						src="https://scripts.simpleanalyticscdn.com/latest.js"
10						strategy="afterInteractive"
11					></Script>
12					<noscript>
13						<img
14							src="https://queue.simpleanalyticscdn.com/noscript.gif"
15							alt="Simple Analytics no script gif"
16							referrerPolicy="no-referrer-when-downgrade"
17							loading="lazy"
18						/>
19					</noscript>
20				</>
21	);
22}

Calling the components

Add the badge to our footer (or wherever).

Footer.tsx
TSX
1import SimpleAnalyticsBadge from '@/components/SimpleAnalyticsBadge';
2
3export default function Footer() {
4	return (
5		<div>
6			<p> © {new Date().getFullYear()}, MySite.com</p>
7			<SimpleAnalyticsBadge />
8		</div>
9	);
10}

Add the script to the entry point of our site. If you're using the latest version of Next with the app router, it will be app/layout.tsx, but for older versions, it could be pages/_app.tsx.

app/layout.tsx
TSX
1import SimpleAnalyticsScript from '@/components/SimpleAnalyticsScript';
2
3export default function RootLayout({ children }: { children: React.ReactNode }) {
4  return (
5    <html>
6    	<body>{children}</body>
7			<SimpleAnalyticsScript />
8    </html>
9  );
10}

Integrated version (recommended)

As I said before, I recommend creating separate components only if you plan to reuse them, so here's how I set up my project.

Footer.tsx

Create and call the SimpleAnalyticsBadge component as a subcomponent.

Footer.tsx
TSX
1import Image from 'next/image';
2import Link from 'next/link';
3
4const footerLinks: FooterLink[] = [
5				...
6];
7
8const bareDomain = 'my-site.com';
9
10function SimpleAnalyticsBadge() {
11	return (
12		<Link
13			href={`https://dashboard.simpleanalytics.com/${bareDomain}?utm_source=${bareDomain}&utm_content=badge`}
14			referrerPolicy="origin"
15			target="_blank"
16		>
17			<picture>
18				<source
19					srcSet={`https://simpleanalyticsbadges.com/${bareDomain}?mode=dark`}
20					media="(prefers-color-scheme: dark)"
21				/>
22				<Image
23					src={`https://simpleanalyticsbadges.com/${bareDomain}?mode=light`}
24					alt="Simple analytics badge"
25					loading="lazy"
26					referrerPolicy="no-referrer"
27					crossOrigin="anonymous"
28					width="201"
29					height="50"
30					unoptimized
31				/>
32			</picture>
33		</Link>
34	);
35}
36
37const FooterLink = ({ href, content }: FooterLink) => {
38	...
39};
40
41export default function Footer() {
42	return (
43		<footer className="mb-16">
44			<ul>
45				{footerLinks.map((link, index) => (
46					<FooterLink key={index} {...link} />
47				))}
48			</ul>
49			<div>
50				<p className=" text-neutral-600 dark:text-neutral-300">
51					© {new Date().getFullYear()}, Dan Edwards
52				</p>
53				<SimpleAnalyticsBadge />
54			</div>
55		</footer>
56	);
57}

Create the SimpleAnalyticsScript component as a subcomponent and call it conditionally.

You should disable the script from loading during development, as you'll get all sorts of console errors if the script is loaded from the wrong address. It will also mess up your page view stats.

I'm using a custom environment module (based on the envalid package) here because I don't play around when it comes to environment variables, and I'm not letting anyone else handle them for me. You can use process.env.NODE_ENV instead, though, which should work fine.

app/layout.tsx
TSX
1import type { Metadata, Viewport } from 'next';
2import Script from 'next/script';
3import './global.tailwind.css';
4
5import { environment } from '@/library/environment';
6
7import Menu from '@/components/Menu';
8import Footer from '@/components/Footer';
9import { Providers } from '@/app/providers';
10
11function SimpleAnalyticsScript() {
12	return (
13		<>
14			<Script
15				async
16				defer
17				src="https://scripts.simpleanalyticscdn.com/latest.js"
18				strategy="afterInteractive"
19			></Script>
20			<noscript>
21				<img
22					src="https://queue.simpleanalyticscdn.com/noscript.gif"
23					alt="Simple Analytics no script gif"
24					referrerPolicy="no-referrer-when-downgrade"
25					loading="lazy"
26				/>
27			</noscript>
28		</>
29	);
30}
31
32export default function RootLayout({
33	children,
34}: {
35	children: React.ReactNode;
36}) {
37	return (
38		<html>
39			<body
40			>
41				<Providers>
42					<main className={clsx('flex-auto flex flex-col min-w-0')}>
43						<Menu />
44						{children}
45						<Footer />
46					</main>
47				</Providers>
48				{environment.isProduction && <SimpleAnalyticsScript />}
49			</body>
50		</html>
51	);
52}

And that's all there is to it. Happy analyzing!