Skip to content

Commit 5e363d1

Browse files
good enough view counter
1 parent 68d78cb commit 5e363d1

File tree

12 files changed

+65
-38
lines changed

12 files changed

+65
-38
lines changed

app/actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use server'
22

3-
import { revalidateTag, unstable_cache, unstable_noStore } from 'next/cache'
3+
import { revalidateTag, unstable_cache } from 'next/cache'
44

55
import { db } from '@/lib/db'
66

@@ -23,6 +23,8 @@ export async function upsertIncreasePostViews({ slug }: { slug: string }) {
2323
},
2424
})
2525
revalidateTag('post-views')
26+
27+
const newRes = await getAllViews()
2628
return result
2729
}
2830

app/api/posts/[slug]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { upsertIncreasePostViews } from '@/app/actions'
2+
export const dynamic = 'force-dynamic'
23

34
export async function POST(request: Request, { params }: { params: Promise<{ slug: string }> }) {
45
try {

app/providers.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { TailwindIndicator } from '@/components/tailwind-indicator'
77
import { ThemeProvider as NextThemesProvider } from 'next-themes'
88
import { Suspense } from 'react'
99
import { NavigationEvents } from '@/components/navigation-events'
10+
import { ReactQueryProvider } from './react-query-provider'
1011

1112
const PHProvider = dynamic(
1213
() => import('@/components/posthog-provider').then((mod) => mod.PHProvider),
@@ -23,18 +24,20 @@ export function Providers({ children }: { children: React.ReactNode }) {
2324
enableSystem
2425
disableTransitionOnChange
2526
>
26-
{/* Render children immediately */}
27-
{children}
27+
<ReactQueryProvider>
28+
{/* Render children immediately */}
29+
{children}
2830

29-
{/* Defer analytics initialization */}
30-
<Suspense fallback={null}>
31-
{process.env.NEXT_PUBLIC_POSTHOG_KEY && (
32-
<PHProvider>
33-
<PostHogPostHogPageView />
34-
</PHProvider>
35-
)}
36-
<NavigationEvents />
37-
</Suspense>
31+
{/* Defer analytics initialization */}
32+
<Suspense fallback={null}>
33+
{process.env.NEXT_PUBLIC_POSTHOG_KEY && (
34+
<PHProvider>
35+
<PostHogPostHogPageView />
36+
</PHProvider>
37+
)}
38+
<NavigationEvents />
39+
</Suspense>
40+
</ReactQueryProvider>
3841
<TailwindIndicator />
3942
</NextThemesProvider>
4043
)

app/react-query-provider.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
5+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
6+
7+
export function ReactQueryProvider({ children }: React.PropsWithChildren) {
8+
const [client] = React.useState(new QueryClient())
9+
10+
return (
11+
<QueryClientProvider client={client}>
12+
{children}
13+
<ReactQueryDevtools initialIsOpen={false} />
14+
</QueryClientProvider>
15+
)
16+
}

app/seo.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface PageSEOProps {
1111

1212
export function genPageMetadata({ title, description, image, ...rest }: PageSEOProps): Metadata {
1313
return {
14+
metadataBase: new URL(siteMetadata.siteUrl),
1415
title,
1516
description: description || siteMetadata.description,
1617
openGraph: {

bun.lockb

1.61 KB
Binary file not shown.

components/navigation-events.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,31 @@
22

33
import { useEffect } from 'react'
44
import { usePathname } from 'next/navigation'
5+
import { useQueryClient } from '@tanstack/react-query'
56

67
export function NavigationEvents() {
78
const pathname = usePathname()
9+
const queryClient = useQueryClient()
810

911
useEffect(() => {
1012
// TODO: create simpler logic
1113
const slug = pathname?.split('/blog/').slice(1).join('/').split('#')[0]
1214

13-
if (slug && process.env.NODE_ENV === 'production') {
15+
if (slug) {
1416
fetch('/api/posts/' + slug, {
1517
method: 'POST',
1618
headers: {
1719
'Content-Type': 'application/json',
1820
},
1921
body: JSON.stringify({}),
2022
})
21-
.then((response) => response)
23+
.then((response) => {
24+
queryClient.invalidateQueries({ queryKey: ['postViews', slug] })
25+
return response
26+
})
2227
.catch((error) => console.error(error))
2328
}
24-
}, [pathname])
29+
}, [pathname, queryClient])
2530

2631
return null
2732
}

components/post-views.tsx

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
'use client'
22

3-
import { useEffect, useState } from 'react'
4-
53
import { getPostViews } from '@/app/actions'
4+
import { useQuery } from '@tanstack/react-query'
65

76
export function PostViews({ slug, prev }: { slug: string; prev: number }) {
87
return (
9-
<span>
8+
<span title="views">
109
<ViewCount slug={slug} prev={prev} /> views
1110
</span>
1211
)
1312
}
1413

1514
function ViewCount({ slug, prev }: { slug: string; prev: number }) {
16-
const [views, setViews] = useState(prev)
17-
18-
useEffect(() => {
19-
// Fetch updated view count
20-
async function fetchViews() {
21-
// Wait 1 second to view to be added to db
22-
await new Promise((resolve) => setTimeout(resolve, 1000))
23-
getPostViews({ slug }).then((count) => {
24-
if (count != null) setViews(count)
25-
})
26-
}
27-
fetchViews()
28-
}, [slug])
29-
30-
return <>{views ?? '-'}</>
15+
const { data } = useQuery({
16+
queryKey: ['postViews', slug],
17+
queryFn: async () => {
18+
// Add artificial delay for smoother UI transition when switching between posts
19+
await new Promise((resolve) => setTimeout(resolve, 500))
20+
return getPostViews({ slug })
21+
},
22+
})
23+
return <>{data != null ? data : prev}</>
3124
}

layouts/ListLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Blog } from 'contentlayer/generated'
88
import Link from '@/components/Link'
99
import Tag from '@/components/Tag'
1010
import siteMetadata from '@/data/siteMetadata'
11+
import { PostViews } from '@/components/post-views'
1112

1213
interface PaginationProps {
1314
totalPages: number
@@ -113,7 +114,7 @@ export default function ListLayout({
113114
<ul>
114115
{!filteredBlogPosts.length && 'No posts found.'}
115116
{displayPosts.map((post) => {
116-
const { path, date, title, summary, tags, viewCount } = post
117+
const { path, date, title, summary, tags, viewCount, slug } = post
117118
return (
118119
<li key={path} className="py-4">
119120
<article className="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
@@ -128,7 +129,7 @@ export default function ListLayout({
128129
<div className="">
129130
<dt className="sr-only ">View count</dt>
130131
<dd className="flex flex-row items-center gap-1 text-sm font-medium text-muted-foreground">
131-
{viewCount} views
132+
<PostViews slug={slug} prev={viewCount} />
132133
</dd>
133134
</div>
134135
)}

layouts/ListLayoutWithTags.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Link from '@/components/Link'
99
import Tag from '@/components/Tag'
1010
import siteMetadata from '@/data/siteMetadata'
1111
import tagData from 'app/tag-data.json'
12+
import { PostViews } from '@/components/post-views'
1213

1314
interface PaginationProps {
1415
totalPages: number
@@ -121,7 +122,7 @@ export default function ListLayoutWithTags({
121122
<div>
122123
<ul>
123124
{displayPosts.map((post) => {
124-
const { path, date, title, summary, tags, viewCount } = post
125+
const { path, date, title, summary, tags, viewCount, slug } = post
125126
return (
126127
<li key={path} className="py-5">
127128
<article className="flex flex-col space-y-2">
@@ -136,7 +137,7 @@ export default function ListLayoutWithTags({
136137
<div className="">
137138
<dt className="sr-only ">View count</dt>
138139
<dd className="flex flex-row items-center gap-1 text-sm font-medium text-muted-foreground">
139-
{viewCount} views
140+
<PostViews slug={slug} prev={viewCount} />
140141
</dd>
141142
</div>
142143
)}

0 commit comments

Comments
 (0)