Skip to content

Commit ec82768

Browse files
authored
post: views count (#5)
* post: views count in blog posts * analytics: don't track non-production environments
1 parent c63bff4 commit ec82768

File tree

11 files changed

+95
-40
lines changed

11 files changed

+95
-40
lines changed

components/post-seo.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextSeo, ArticleJsonLd } from 'next-seo';
2+
import { siteBaseUrl } from '@/lib/constants';
23

34
const PostSeo = ({
45
author,
@@ -11,9 +12,7 @@ const PostSeo = ({
1112
}) => {
1213
const publishedAt = new Date(date).toISOString();
1314
const featuredImage = {
14-
url: image
15-
? `https://arturocampos.dev${image}`
16-
: 'https://arturocampos.dev/image/og.png',
15+
url: image ? `${siteBaseUrl}${image}` : `${siteBaseUrl}/image/og.png`,
1716
alt: title,
1817
};
1918

i18n/strings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const LangStrings = {
2323
slogan: 'Arturo Campos - Web Development & Technology Blog',
2424
sorry: 'Sorry',
2525
uses: 'Uses',
26+
view: 'view',
27+
views: 'views',
2628
},
2729
es: {
2830
about: 'Acerca de',
@@ -48,6 +50,8 @@ const LangStrings = {
4850
slogan: 'Arturo Campos - Blog de Desarrollo Web y Tecnología',
4951
sorry: 'Lo siento',
5052
uses: 'Uso',
53+
view: 'vista',
54+
views: 'vistas',
5155
},
5256
};
5357

lib/constants.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,13 @@ const dateOptions = {
44
day: 'numeric',
55
};
66

7-
export { dateOptions };
7+
const description =
8+
'Front-end Engineer, JavaScript enthusiast, and proud father';
9+
10+
const domain = 'arturocampos.dev';
11+
12+
const siteBaseUrl = `https://${domain}`;
13+
14+
const title = 'Arturo Campos – Front-end Engineer';
15+
16+
export { dateOptions, description, domain, siteBaseUrl, title };

lib/goat-counter.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1+
import { domain } from '@/lib/constants';
2+
13
const pageView = url => {
2-
window.goatcounter?.count({
3-
path: url,
4-
});
4+
if (window?.location?.host === domain) {
5+
window.goatcounter?.count({
6+
path: url,
7+
});
8+
}
9+
};
10+
11+
const getPathViews = async path => {
12+
try {
13+
const views = await fetch(
14+
`https://arturocampos.goatcounter.com/counter/${encodeURIComponent(
15+
path
16+
)}.json`
17+
);
18+
const { count_unique } = await views.json();
19+
return count_unique;
20+
} catch (_) {
21+
return 0;
22+
}
523
};
624

7-
export { pageView };
25+
export { pageView, getPathViews };

lib/util.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Image from 'next/image';
22

33
import Vimeo from '@/components/vimeo';
44
import YouTube from '@/components/youtube';
5-
import { siteBaseUrl } from '../next-seo.config.js';
65

76
const checkForComponentUse = (tagName, str) => {
87
const exp = new RegExp(`<${tagName}`);
@@ -32,9 +31,9 @@ const getComponents = (hydrationComponentsList = []) => {
3231
return componentsList;
3332
};
3433

35-
// Returns the localized URL given a locale and a path
36-
const getLocalizedUrl = ({ defaultLocale, locale, asPath: path }) => {
37-
return `${siteBaseUrl}${defaultLocale == locale ? '' : `/${locale}`}${path}`;
34+
// Returns the localized path given a locale and a path
35+
const getLocalizedPath = ({ defaultLocale, locale, asPath: path }) => {
36+
return `${defaultLocale == locale ? '' : `/${locale}`}${path}`;
3837
};
3938

40-
export { getComponents, getHydrationComponentsList, getLocalizedUrl };
39+
export { getComponents, getHydrationComponentsList, getLocalizedPath };

next-seo.config.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
const title = 'Arturo Campos – Front-end Engineer';
2-
const description =
3-
'Front-end Engineer, JavaScript enthusiast, and proud father';
4-
const siteBaseUrl = 'https://arturocampos.dev';
1+
import { description, siteBaseUrl, title } from '@/lib/constants';
52

63
const SEO = {
74
title,
@@ -29,5 +26,4 @@ const SEO = {
2926
},
3027
};
3128

32-
export { siteBaseUrl };
3329
export default SEO;

pages/_document.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Document, { Html, Head, Main, NextScript } from 'next/document';
2+
import { domain } from '@/lib/constants';
23
import theme from '@/lib/theme';
34

45
class MyDocument extends Document {
@@ -39,6 +40,16 @@ class MyDocument extends Document {
3940
rel='stylesheet'
4041
href='https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'
4142
/>
43+
<script
44+
dangerouslySetInnerHTML={{
45+
__html: `
46+
// Only load on production environment.
47+
if (window.location.host !== '${domain}') {
48+
window.goatcounter = {no_onload: true};
49+
}
50+
`,
51+
}}
52+
/>
4253
<script
4354
async
4455
data-goatcounter='https://arturocampos.goatcounter.com/count'

pages/blog/[slug].js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from 'react';
12
import Link from 'next/link';
23
import Image from 'next/image';
34
import { useRouter } from 'next/router';
@@ -7,22 +8,33 @@ import Heading from '@/components/heading';
78
import PostSeo from '@/components/post-seo';
89
import useTranslation from '@/i18n/useTranslation';
910
import { getAllPostSlugs, getContent } from '@/lib/content';
10-
import { dateOptions } from '@/lib/constants';
11-
import { getComponents, getLocalizedUrl } from '@/lib/util';
11+
import { dateOptions, siteBaseUrl } from '@/lib/constants';
12+
import { getPathViews } from '@/lib/goat-counter';
13+
import { getComponents, getLocalizedPath } from '@/lib/util';
1214

1315
const Post = ({ mdxSource, frontMatter, hydrationComponentsList }) => {
1416
const { t } = useTranslation();
1517
const router = useRouter();
1618
const { locale } = router;
19+
const [views, setViews] = useState(0);
1720
const content = hydrate(mdxSource, {
1821
components: getComponents(hydrationComponentsList),
1922
});
20-
const localizedUrl = getLocalizedUrl(router);
23+
const localizedPath = getLocalizedPath(router);
2124
const { image: imagePath } = frontMatter;
2225

26+
useEffect(async () => {
27+
const viewsCount = await getPathViews(localizedPath);
28+
setViews(viewsCount);
29+
}, []);
30+
2331
return (
2432
<>
25-
<PostSeo locale={locale} url={localizedUrl} {...frontMatter} />
33+
<PostSeo
34+
locale={locale}
35+
url={`${siteBaseUrl}${localizedPath}`}
36+
{...frontMatter}
37+
/>
2638
<div className='flex items-center px-2 py-1 mb-4 space-x-1 overflow-hidden text-xs font-medium text-gray-600 uppercase bg-gray-100 border border-gray-300 rounded-md'>
2739
<Link href='/'>
2840
<a className='inline-flex items-center'>{t('home')}</a>
@@ -36,9 +48,14 @@ const Post = ({ mdxSource, frontMatter, hydrationComponentsList }) => {
3648
</div>
3749
<article>
3850
<Heading>{frontMatter.title}</Heading>
39-
<time className='block my-2 text-sm text-center text-gray-600 md:text-left'>
40-
{new Date(frontMatter.date).toLocaleDateString(locale, dateOptions)}
41-
</time>
51+
<div className='flex justify-between my-2 text-sm text-gray-600'>
52+
<time>
53+
{new Date(frontMatter.date).toLocaleDateString(locale, dateOptions)}
54+
</time>
55+
<span>
56+
{views} {views !== 1 ? t('views') : t('view')}
57+
</span>
58+
</div>
4259
{imagePath ? (
4360
<picture className='block mx-auto my-3 max-w-media'>
4461
<Image

pages/blog/index.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import { NextSeo } from 'next-seo';
77
import Heading from '@/components/heading';
88
import ReadMore from '@/components/read-more';
99
import useTranslation from '@/i18n/useTranslation';
10-
import { dateOptions } from '@/lib/constants';
10+
import { dateOptions, siteBaseUrl } from '@/lib/constants';
1111
import { getSortedPostsData } from '@/lib/content';
12-
import { getLocalizedUrl } from '@/lib/util';
12+
import { getLocalizedPath } from '@/lib/util';
1313

1414
const Blog = ({ allPostsData = [] }) => {
1515
const { t } = useTranslation();
1616
const router = useRouter();
1717
const { locale } = router;
18-
const localizedUrl = getLocalizedUrl(router);
18+
const localizedPath = getLocalizedPath(router);
1919
const title = `${t('blog')} - Arturo Campos`;
2020
const description = t('blog-description');
2121

@@ -32,10 +32,10 @@ const Blog = ({ allPostsData = [] }) => {
3232
<>
3333
<NextSeo
3434
title={title}
35-
canonical={localizedUrl}
35+
canonical={`${siteBaseUrl}${localizedPath}`}
3636
description={description}
3737
openGraph={{
38-
url: localizedUrl,
38+
url: `${siteBaseUrl}${localizedPath}`,
3939
locale,
4040
title,
4141
description,
@@ -45,10 +45,10 @@ const Blog = ({ allPostsData = [] }) => {
4545
pagedPosts.map(post => (
4646
<article key={post.slug} className='mb-4 border-b last:border-b-0'>
4747
<Heading linkTo={`/blog/${post.slug}`}>
48-
{post.frontMatter.title}
48+
{post.frontMatter?.title}
4949
</Heading>
5050
<time className='block my-2 text-sm text-center text-gray-600 md:text-left'>
51-
{new Date(post.frontMatter.date).toLocaleDateString(
51+
{new Date(post.frontMatter?.date).toLocaleDateString(
5252
locale,
5353
dateOptions
5454
)}

pages/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,28 @@ import { NextSeo } from 'next-seo';
33
import hydrate from 'next-mdx-remote/hydrate';
44

55
import Heading from '@/components/heading';
6+
import { siteBaseUrl } from '@/lib/constants';
67
import { getContent } from '@/lib/content';
7-
import { getComponents, getLocalizedUrl } from '@/lib/util';
8+
import { getComponents, getLocalizedPath } from '@/lib/util';
89

910
const Home = ({ mdxSource, frontMatter, hydrationComponentsList }) => {
1011
const router = useRouter();
1112
const { locale } = router;
1213
const content = hydrate(mdxSource, {
1314
components: getComponents(hydrationComponentsList),
1415
});
15-
const localizedUrl = getLocalizedUrl(router);
16+
const localizedPath = getLocalizedPath(router);
1617
const { description } = frontMatter;
1718

1819
return (
1920
<>
2021
<NextSeo
21-
canonical={localizedUrl}
22+
canonical={`${siteBaseUrl}${localizedPath}`}
2223
description={description}
2324
openGraph={{
2425
description,
2526
locale,
26-
url: localizedUrl,
27+
url: `${siteBaseUrl}${localizedPath}`,
2728
}}
2829
/>
2930
<Heading className='md:text-center'>{frontMatter.title}</Heading>

0 commit comments

Comments
 (0)