adoc Studio for Mac, iPad & iPhone
Learn how to track page views, scroll depth, and feedback in AsciiDoc documentation. Includes ready-to-use code snippets for HTML export and PDF measurement.
You can precisely measure the success of your documentation. To do this, you need two things:
In this article, I'll proceed step by step: First, we'll clarify which metrics are truly meaningful for your documentation, such as page views, reading time, scroll depth, PDF download rates, or clicks on specific call-to-actions.
Then we'll look at what technical prerequisites must be in place, for example a web analytics tool, clean URLs, consistent UTM parameters, and a clear structure for your AsciiDoc sources.
Finally, I'll show you how to concretely implement these metrics in AsciiDoc, for instance by integrating tracking links and event IDs directly into the source text, so they are automatically included in HTML, PDF, and other web exports.
Prerequisites for Tracking to Work
Before discussing metrics, make sure you have:
Unique URLs and IDs
Each documentation page has a stable URL.
Each page has a unique identifier (doc-id).
This identifier appears in the HTML, in the PDF (indirectly), and in events.
Central Template or Include
No tracking code in each AsciiDoc document individually.
A shared include or layout template that reads the doc-id and embeds analytics or logging code.
Analytics or Logging System
Typical options:
Web analytics (e.g., Matomo On-Prem, Plausible, GA4).
Custom backend (events via REST API).
Combination with support system (Zendesk, Freshdesk, etc.) for deflection.
Consent / Data Privacy
Proper consent when collecting personal or pseudonymous user data.
Ideally: self-hosted, data-minimal solution.
Metric Categories and How to Implement Them
| Metric | Effort | Business Impact | Requirements |
|---|---|---|---|
| Page Views | ⭐ | ⭐⭐ | Analytics |
| Unique Users | ⭐ | ⭐⭐ | Analytics |
| Scroll Depth | ⭐⭐ | ⭐⭐⭐ | JS + Backend |
| On-Page Feedback | ⭐⭐ | ⭐⭐⭐⭐ | Widget + Backend |
| Search & Findability | ⭐⭐ | ⭐⭐⭐ | Search Logging |
| Task Success | ⭐⭐⭐ | ⭐⭐⭐⭐ | Funnel Tracking |
| Ticket Deflection | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Support Integration |
| Internal Quality Metrics | ⭐⭐ | ⭐⭐⭐ | CI/Linter |
| PDF Tracking (Links) | ⭐ | ⭐⭐ | Redirect URLs |
| PDF Tracking (QR) | ⭐⭐ | ⭐⭐ | QR Generator |
1. Page Views
What: How often a page was viewed. Basic metric for usage.
Technical Prerequisites
Stable URL per page.
Tracking snippet in the HTML template.
doc-idas unique identifier.
AsciiDoc Example
In the header of each page:
= Getting Started with Product X
:doc-id: getting-started
:doc-area: onboarding
:doc-version: 1.4
In the HTML template or as an include:
ifdef::backend-html5[]
++++
<script>
(function() {
// Provide attributes via meta tags or data attributes
const docMeta = document.querySelector('meta[name="doc-id"]');
const areaMeta = document.querySelector('meta[name="doc-area"]');
const versionMeta = document.querySelector('meta[name="doc-version"]');
const docId = docMeta ? docMeta.content : 'unknown';
const area = areaMeta ? areaMeta.content : 'unknown';
const version = versionMeta ? versionMeta.content : 'unknown';
// Send pageview to your system
// pageview({ page: window.location.pathname, docId, area, version });
})();
</script>
++++
endif::[]
Important Note: AsciiDoc does not substitute attributes inside passthrough blocks (++++...++++) by default. Instead, use meta tags in the document header or data attributes on a container element, which you then read via JavaScript. See the appendix for details.
Where to Measure?
In your analytics under "Pages" by page or docId. You can:
Group views by doc-area (e.g., Onboarding, API, Admin).
Compare releases (doc-version).
Identify top and low performers.
You don't just see "lots of traffic," but clearly: which page, which area, which version.
2. Unique Users & Returning Visitors
What: How many people/browsers use your documentation and whether they return.
Technical Prerequisites
Analytics tool with sessions/user logic.
Same tracking snippet as above.
No additional code needed in AsciiDoc. What matters is that all documentation pages use the snippet.
Where to Measure?
Standard reports in analytics:
"Users / Unique Visitors"
"New vs. Returning"
Filterable by paths or doc-id areas.
Example: Only doc-area=api → How many users regularly work with API documentation?
3. Time on Page & Scroll Depth
What: Do users read the content or do they leave early?
Technical Prerequisites
JavaScript enabled.
Events sent to analytics or custom backend.
AsciiDoc Integration: Global include – use sendBeacon() for reliable transmission:
ifdef::backend-html5[]
++++
<script>
(function() {
const docMeta = document.querySelector('meta[name="doc-id"]');
const docId = docMeta ? docMeta.content : 'unknown';
let maxScroll = 0;
window.addEventListener('scroll', () => {
const scrolled = (window.scrollY + window.innerHeight)
/ document.body.scrollHeight * 100;
if (scrolled > maxScroll) {
maxScroll = scrolled;
}
});
// sendBeacon is more reliable than fetch in beforeunload
window.addEventListener('beforeunload', () => {
const data = JSON.stringify({
docId: docId,
depth: Math.round(maxScroll)
});
navigator.sendBeacon('/api/scroll-depth', data);
});
})();
</script>
++++
endif::[]
Note: The beforeunload event often blocks asynchronous requests in modern browsers. navigator.sendBeacon() solves this problem by reliably sending data even when the tab is closed. Alternatively, you can send events periodically (e.g., every 30 seconds).
Where to Measure?
In your event dashboard: Average scroll depth per doc-id.
Interpretation:
<25%: Title/intro doesn't match or content is irrelevant.
50–75%: normal.
~100% on troubleshooting pages: good, users are actively looking for a solution.
4. On-Page Feedback ("Was This Helpful?")
What: Quick quality rating per page.
Technical Prerequisites
Small HTML widget.
Event endpoint in the backend or analytics as event target.
AsciiDoc Example
ifdef::backend-html5[]
++++
<div id="doc-feedback">
Was this page helpful?
<button data-feedback="yes">Yes</button>
<button data-feedback="no">No</button>
</div>
<script>
(function() {
const docMeta = document.querySelector('meta[name="doc-id"]');
const docId = docMeta ? docMeta.content : 'unknown';
document.querySelectorAll('#doc-feedback button')
.forEach(btn => btn.addEventListener('click', () => {
const value = btn.dataset.feedback;
// feedbackEvent({ docId, value });
}));
})();
</script>
++++
endif::[]
Where to Measure?
Aggregation per doc-id:
helpful_rate = yes / (yes + no)
Benefits:
Ranking: Which pages deliver the most value?
Prioritization: Improve pages with many views + poor ratings first.
5. Search & Findability
What: Do users find what they need?
Technical Prerequisites
Search function in the documentation portal.
Logging of search terms + results.
AsciiDoc Integration
If your search runs in the frontend:
ifdef::backend-html5[]
++++
<form id="doc-search">
<input name="q" />
<button type="submit">Search</button>
</form>
<script>
document.getElementById('doc-search')
.addEventListener('submit', function(e) {
e.preventDefault();
const query = this.q.value;
// searchEvent({ query });
// Then execute actual search
});
</script>
++++
endif::[]
Where to Measure?
List of all search queries.
Search queries with no results.
Terms that lead to wrong pages (high bounce rate).
From this, you optimize: page titles, synonyms, and content structure.
6. Task Success & Ticket Deflection
This is where you connect documentation with support costs. Internally, this is often the strongest lever.
a) Task Success
What: Percentage of users who successfully complete a defined task with the help of documentation.
Technical Prerequisites
Defined task flows (e.g., "Set up API key").
Pages belonging to this flow carry an attribute.
AsciiDoc Example
= Create API Key
:doc-id: api-key-create
:task-flow: api-key-setup
In the tracking snippet (meta tag approach):
<script>
const flowMeta = document.querySelector('meta[name="task-flow"]');
const docMeta = document.querySelector('meta[name="doc-id"]');
if (flowMeta && docMeta) {
// taskStepEvent({
// flow: flowMeta.content,
// docId: docMeta.content
// });
}
</script>
Success Measurement: Success when users reach the target page (e.g., "Successfully connected" page) or perform an action (e.g., save API key; event from product).
Where to Measure? Funnel per task-flow: Entry → Intermediate steps → Success. You see where users drop off and which pages in the flow need optimization.
b) Ticket Deflection
What: How many support requests does your content prevent?
Technical Prerequisites
Support form or help widget with article reference.
Mapping of tickets to
doc-id.
Concrete Approach: In the help widget, users see article suggestions. If they still create a ticket, send the last viewed doc-id along.
Analysis:
Topics with many tickets despite documentation → content unclear.
Drop in tickets after article update → measurable success.
7. Internal Quality Metrics
These metrics are directly tied to your AsciiDoc setup.
Examples:
Percentage of outdated pages (
:doc-version:vs. product version).Broken links (via link check in CI).
Update time after release (commit timestamp).
AsciiDoc Support: Attributes like :last-review: 2025-10-15. Tools/linters read these attributes and generate reports.
Making PDF Exports Measurable
PDFs cannot send JS events. You make them measurable through indirect signals.
8. Trackable Links from PDF
Use a unique short URL per PDF:
More info:
link:https://docs.example.com/go/getting-started-pdf[Online Documentation]
This URL redirects internally to the actual page. You measure:
How often readers jump from the PDF back to online documentation.
Which PDFs are actually being used.
9. QR Codes
Use doc-id to generate QR codes.
image::qrcode-getting-started.png[Give Feedback]
QR target e.g.: https://docs.example.com/f/{doc-id}
There: Feedback form or link to the updated version.
10. Metadata
Set in the PDF generator: Title, Subject, Keywords with doc-id and product. This way, internal systems (DAM, intranet, DMS) can better track usage and versions.
Practical Minimal Implementation for Your Team
If you want to start with minimal effort, this setup is sufficient:
In each AsciiDoc page:
= Title
:doc-id: <unique>
:doc-area: <area>
:doc-version: <version>
A Global HTML Include
Pageview with doc-id (via meta tags or data attributes).
Scroll depth event (with
sendBeaconfor reliability).Optional feedback buttons.
A Simple Backend or Analytics
Per doc-id:
Pageviews
Scroll depth
Feedback rate
Monthly Evaluation
Top 20 pages by views.
Pages with many views and poor feedback.
Search terms with no results.
Tickets per topic before/after documentation updates.
This creates measurable value: You see in black and white which content works and where you need to improve.
Appendix: Technical Implementation Notes
Attribute Substitution in AsciiDoc
AsciiDoc does not substitute attributes inside passthrough blocks (++++...++++) by default. This is a common pitfall.
Solution 1 – Meta Tags in Document Header:
Generate meta tags outside the passthrough block and read them via JavaScript.
:doc-id: getting-started
// In docinfo header or template:
<meta name="doc-id" content="{doc-id}">
The meta tag is correctly substituted because it's outside the passthrough block. In JavaScript, you then read:
const docId = document.querySelector('meta[name="doc-id"]').content;
Solution 2 – subs Attribute:
In some AsciiDoc versions, you can use [subs=attributes] before the passthrough block:
[subs=attributes]
++++
<script>
const docId = '{doc-id}';
</script>
++++
Test this with your AsciiDoc version, as behavior may vary.
Solution 3 – Data Attributes on Container Elements:
[id="tracking-container",data-doc-id="{doc-id}"]
--
Content
--
beforeunload and sendBeacon
The beforeunload event is problematic for analytics:
Modern browsers often block asynchronous requests (
fetch,XMLHttpRequest).Mobile browsers frequently suppress the event completely.
Safari has particularly strict restrictions.
Recommendation: Use navigator.sendBeacon(). This method was specifically designed for this use case and reliably sends data even when the tab is closed.
window.addEventListener('beforeunload', () => {
const data = JSON.stringify({ docId, depth: maxScroll });
navigator.sendBeacon('/api/analytics', data);
});
Alternative: Send events periodically (e.g., every 30 seconds) instead of only when leaving. This way, you lose at most 30 seconds of data.
setInterval(() => {
navigator.sendBeacon('/api/scroll-depth', JSON.stringify({
docId,
depth: Math.round(maxScroll)
}));
}, 30000);