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
:
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 accessibilityloading="lazy"
as you'll probably have this below the fold in the footerheight
andwidth
to avoid layout shiftunoptimised
Without this, Next.js will look for the image athttps://my-site.com/https://https://simpleanalyticsbadges.com/my-site.com
.
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.
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).
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.
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.
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.
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!