genko.meDocs
SDK and CLI

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.

app/layout.tsx
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.

tsx
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.

tsx
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.

tsx
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.

PropComponentUsage
workspaceBothWorkspace slug; required by the framework-neutral React components
baseUrlBothDefaults to https://api.genko.me
entryIdBothPublic entry ID
pathBothPageView defaults to the current path; ViewCount uses this as the display target
formatViewCountFunction that receives the number and returns a ReactNode
fallbackViewCountContent shown while loading or on failure; renders nothing by default
localeViewCountNumber-formatting locale; defaults to ja-JP
classNameViewCountCSS 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.

ts
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.

ts
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"]);

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.

tsx
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

On this page