Page views
Count views on your site, connect them to CMS entries, and see the results in genko.
Track page views on your own site and see them in your genko workspace. Counts can be tied to CMS entries as well as page paths, so you can see which articles people read.
Open PV, next to Media, in the workspace. Each entry's view count also appears below its character count in the content list.
Register accepted domains
On the PV page, register the domains you want to count. Only owners and maintainers can manage them. You can register up to 20 domains; register each subdomain separately. Views from other domains are ignored, and localhost is always ignored.
Add PageView to your layout
For Next.js App Router, import from @genko-me/react/next. Place one component in the layout to count each page path. It renders nothing.
import type { ReactNode } from "react";import { PageView } from "@genko-me/react/next";
export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> {children} <PageView workspace="your-workspace" /> </body> </html> );}Set workspace to your workspace slug. No API key is needed. After the first view arrives, the dashboard replaces the setup steps with charts. It checks for new data every 10 seconds. Test on a site with a registered domain.
Count entry views too
Add another component on an entry page, passing the entry's id. This increments only the entry count; it doesn't count the path a second time. Entry counts are keyed by public entry ID across the workspace, so entries with the same ID in different APIs share one count.
import { PageView, ViewCount } from "@genko-me/react/next";
export function ArticleViews({ entry }: { entry: { id: string } }) { return ( <> <PageView workspace="your-workspace" entryId={entry.id} /> <ViewCount workspace="your-workspace" entryId={entry.id} /> </> );}React and routing
Outside Next.js, import from @genko-me/react. Without path, the component reads location.pathname and follows back/forward navigation through popstate. For a custom router, pass its current pathname as path. The component sends a view whenever that value changes.
import { PageView } from "@genko-me/react";
export function SitePageViews({ pathname }: { pathname: string }) { return <PageView workspace="your-workspace" path={pathname} />;}The Next.js adapter follows usePathname(), including App Router navigation. Vue, Svelte, and Web Components versions are coming later. You can use the JavaScript functions below in the meantime.
Display a count
ViewCount only displays an all-time count. It never records a view. Use entryId for an entry or path for a page. If both are set, entryId takes precedence.
import { ViewCount } from "@genko-me/react";
export function ArticleCount({ entryId }: { entryId: string }) { return ( <ViewCount workspace="your-workspace" entryId={entryId} format={count => `${count.toLocaleString("en-US")} views`} fallback={<span>ā</span>} /> );}For a page path, use <ViewCount workspace="your-workspace" path="/blog/hello" />. Counts are cached for up to about five minutes, so the displayed number can lag behind new views.
| Prop | Component | Usage |
|---|---|---|
workspace | Both | Workspace slug; required by the framework-neutral React components |
baseUrl | Both | Defaults to https://api.genko.me |
entryId | Both | Public entry ID |
path | Both | PageView defaults to the current path; ViewCount uses this as the display target |
format | ViewCount | Function that receives the number and returns a ReactNode |
fallback | ViewCount | Content shown while loading or on failure; renders nothing by default |
locale | ViewCount | Number-formatting locale; defaults to ja-JP |
className | ViewCount | CSS class for the rendered span |
Use the Next.js versions from server components. Instead of workspace, you can pass the same api={client.blog} used by List and View. The API key stays on the server. Next.js ViewCount also accepts cache, the number of seconds to reuse fetched results, defaulting to 300. Supply any format function on the server too.
JavaScript and other frameworks
Call sendPageView in the browser when a page is displayed. In a single-page app, call it after route changes as well.
import { sendPageView } from "@genko-me/core/browser";
sendPageView({ workspace: "your-workspace", path: location.pathname });// On entry pages, increment the entry count separately.sendPageView({ workspace: "your-workspace", entryId: "abcDEF123456" });Use fetchViewCounts to read all-time counts. It also works on the server and throws if fetching fails.
import { fetchViewCounts } from "@genko-me/core";
const counts = await fetchViewCounts({ workspace: "your-workspace", entryIds: ["abcDEF123456"], paths: ["/blog/hello"],});console.log(counts.entries.abcDEF123456, counts.paths["/blog/hello"]);Show popular entries first
Use orders="-views" on a delivery list for most viewed first, or orders="views" for least viewed first. Sorting uses all-time entry counts; entries without views count as zero.
import { List, ListItem } from "@genko-me/react/next";import { client } from "@/lib/client";
export default function PopularArticles() { return ( <List api={client.blog} orders="-views" limit={4} href="/blog/{id}"> <h2><ListItem id="title" /></h2> </List> );}The SDK equivalent is genko.apis.blog.list({ orders: "-views", limit: 4 }). To fetch all-time and recent counts for entries in one API, use the authenticated GET /v1/{workspace}/_views/{endpoint}?ids=a,b&days=30 endpoint. In the SDK, use genko.apis.blog.views({ ids: ["abcDEF123456"], days: 30 }). The low-level client exposes client.getViews(...); see the SDK reference. The delivery API reference describes the HTTP request and response.
Counting and stored data
Views from the same device on the same page count once per 30 minutes. Duplicate detection temporarily uses a salted hash derived from the IP address and User-Agent, with the salt rotating daily. The IP address and User-Agent themselves are not stored.
The stored view data consists only of daily counts per page path and per entry. Query strings and hashes are removed from paths; repeated slashes and trailing slashes are normalized too. No cookies are used. Referrers, device information, countries, and visitor tracking data are not stored.
Bots and automated browsers (navigator.webdriver) aren't counted, and requests are rate-limited per IP. Sending uses sendBeacon with text/plain, avoiding a CORS preflight, with a fetch fallback. Failures don't affect your page. See the delivery API reference for the public endpoints.
You may want to mention in your site's privacy policy that page paths are sent to genko (genko.me).
Pricing
Counting is currently free. Future free allowances and usage pricing have not been decided.
Last updated: October 19, 2026