Linking
A robust linking system that survives content reorganization and works seamlessly in single-page applications.
The Problem with Traditional Links
In traditional websites, links are tied to URL paths:
[Installation Guide](/docs/getting-started/installation)
[Contact Section](/about#contact-form)This works until you reorganize your content. Move "Installation" under a new "Setup" section? Every link breaks. Rename a page? Broken links. Reorder sections on a page? Hash links stop working.
Documentation sites are particularly vulnerable—content restructuring is routine as projects evolve. Maintaining hundreds of internal links becomes a burden that discourages improvement.
Stable References
Uniweb solves this with a stable reference system that decouples a page's identity from its location in the site hierarchy.
[Installation Guide](page:installation)
[Contact Section](page:about#contact)These links resolve to the correct URL regardless of where the content lives. Move "Installation" anywhere in your site—the link still works.
How It Works
Section IDs (Automatic)
Every section gets a stable ID derived from its filename:
pages/home/
├── 1-hero.md → #section-hero
├── 2-features.md → #section-features
├── 2.5-testimonials.md → #section-testimonials
└── 3-cta.md → #section-ctaThe numeric prefix is for ordering only—the ID comes from the name part. You can freely reorder sections (change 2-features.md to 5-features.md) without breaking any links to #section-features.
Override when needed via frontmatter:
---
type: ContactForm
id: contact
---Page IDs (Optional)
By default, pages are referenced by their route path, and the homepage's route is /:
[About Us](page:about)
[API Docs](page:docs/api)
[Home](page:/)A page is named by its route or by its ID, never by its folder: the homepage lives in pages/home/ but is page:/, unless you give it an ID.
For pages that might move, add an explicit ID in page.yml:
# pages/docs/getting-started/installation/page.yml
id: installation
title: Installation GuideNow page:installation resolves correctly even if you later move the page to pages/setup/install/.
Sites you also edit in the Uniweb app
The app links a page by an ID it creates, a code like 3f58142d, which it keeps in the page's page.yml. uniweb pull writes the app's links as the page's path, [AI](page:ai), and uniweb push stores them by the code again, so they keep working when the page is renamed in the app. An ID you chose yourself, like installation, is written as you wrote it. Leave the id: the app put in a page.yml: it is how a push knows which page a path names.
Link Syntax
The page: protocol supports pages, sections, and combinations:
| Syntax | Resolves To |
|---|---|
page:about | /about |
page:docs/api | /docs/api |
page:/ | / (the homepage) |
page:/#features | /#section-features |
page:about#contact | /about#section-contact |
page:installation | Route of page with id: installation |
page:installation#requirements | Same page, scrolls to requirements section |
In Markdown Content
For setup instructions, see the [Installation Guide](page:installation).
Have questions? Visit our [contact form](page:about#contact).Links written as files
Docs kept in a repository link to each other by file, because that's what works on GitHub and in an editor. They work on the site too. A relative link to a markdown file leads to the page that file became, built or pushed, and its anchor to that page's heading, whether it's GitHub's anchor or the site's:
[Deploying](./deploying.md) <!-- /docs/guides/deploying -->
[Setup](../guides/deploying.md#first-setup) <!-- /docs/guides/deploying#first-setup -->A file that didn't become a page (a README.md, one that's been moved) stays as written, and the build names it, since it leads nowhere. Use page: for a link that should survive moving the file.
Cross-Page Scrolling
When navigating to a page with a hash (e.g., page:about#contact), the runtime:
- Navigates to the page via client-side routing
- Waits for the page to render
- Smoothly scrolls to the target section
This works in both SPA mode (development) and with pre-rendered pages (production).
When to Use Explicit IDs
Section IDs are automatic—you rarely need to override them. Do so only when:
- The filename doesn't reflect the section's purpose
- You want a shorter/cleaner hash URL
- Multiple sections could have similar auto-generated IDs
Page IDs are optional. Add them when:
- The page is frequently linked to from other pages
- You anticipate reorganizing your site structure
- You want URLs to survive localization (same ID across
/en/and/es/)
Comparison with Other Approaches
| Approach | Pros | Cons |
|---|---|---|
| Path-based links | Simple, familiar | Break on reorganization |
| Link checking tools | Catch broken links | Fix after the fact, maintenance burden |
| CMS-managed links | Centralized | Requires CMS, vendor lock-in |
| Uniweb stable IDs | Automatic, survives changes | Requires page: protocol |
Best Practices
Use
page:for internal links—plain paths still work but don't get the stability benefitsLet section IDs be automatic—meaningful filenames like
2-features.mdgive you#section-featuresfor freeAdd page IDs proactively for high-traffic pages in documentation sites
Keep IDs short and descriptive—they appear in URLs when users share links
Don't change IDs once content is published—they're part of your public API
See Also
- Page Configuration — Setting page IDs in page.yml
- Content Structure — Section IDs from filenames
- Navigation Patterns — Building navigation with stable links
- Internationalization — Locale-aware link resolution