Hillnote Wiki components for Next.js projects
npm install @hillnote/wikiTurn your Hillnote workspace into a beautiful wiki for your Next.js projects. This package provides ready-to-use components for creating documentation sites powered by your Hillnote workspace.
- š Beautiful documentation UI with navigation sidebar
- šØ Dark/Light theme support
- š± Fully responsive design
- š SEO-friendly with sitemap generation
- š¤ AI-crawler optimized with structured data
- š Auto-expanding navigation for active documents
- š Smart URL routing with slugs
- š Markdown rendering with syntax highlighting
``bash`
npm install @hillnote/wikior
yarn add @hillnote/wikior
pnpm add @hillnote/wiki
Note: All required dependencies (@radix-ui components, gray-matter, etc.) will be automatically installed with the package.
First, create a providers component and update your root layout:
`jsx
// app/providers.js
"use client"
import { ThemeProvider } from '@hillnote/wiki'
export function Providers({ children }) {
return (
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
)
}
`
`jsx
// app/layout.js
import { Providers } from "./providers"
import "@hillnote/wiki/styles"
import "./globals.css"
export default function RootLayout({ children }) {
return (
$3
Create the main wiki page and dynamic routing:
`jsx
// app/page.js
"use client"import { Document, Navbar, ConfigProvider, pathToSlug, initializeSlugMapping } from '@hillnote/wiki'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
const wikiConfig = {
siteName: "My Wiki",
workspace: {
path: "/workspace/", // Path to your Hillnote workspace in public folder
enabled: true,
documentsFolder: "documents",
registryFile: "documents-registry.json",
initialFile: "documents/Start Here.md"
},
ui: {
authorsNotes: true,
navigationText: "All Pages"
}
}
export default function WikiPage() {
const [mounted, setMounted] = useState(false)
const router = useRouter()
useEffect(() => {
setMounted(true)
// Initialize slug mapping on mount
initializeSlugMapping(wikiConfig)
}, [])
const handleFileSelect = (filePath) => {
if (!filePath) return
// Convert file path to URL slug
const slug = pathToSlug(filePath)
router.push(
/doc/${slug})
} if (!mounted) return null
return (
siteConfig={wikiConfig}
initialFile={wikiConfig.workspace.initialFile}
showNavigation={true}
showTableOfContents={true}
onFileSelect={handleFileSelect}
/>
)
}
``jsx
// app/doc/[...path]/page.js
"use client"import { Document, Navbar, ConfigProvider, pathToSlug, slugToPath, initializeSlugMapping } from '@hillnote/wiki'
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
const wikiConfig = {
siteName: "My Wiki",
workspace: {
path: "/workspace/",
enabled: true,
documentsFolder: "documents",
registryFile: "documents-registry.json",
initialFile: "documents/Start Here.md"
},
ui: {
authorsNotes: true,
navigationText: "All Pages"
}
}
export default function DocumentPage() {
const params = useParams()
const router = useRouter()
const [mounted, setMounted] = useState(false)
const [filePath, setFilePath] = useState(wikiConfig.workspace.initialFile)
// Get the slug from URL params
const slug = params.path ? params.path.join('/') : null
useEffect(() => {
setMounted(true)
// Initialize slug mapping and then set the file path
const initAndSetPath = async () => {
await initializeSlugMapping(wikiConfig)
if (slug) {
const path = slugToPath(slug)
setFilePath(path)
}
}
initAndSetPath()
}, [slug])
const handleFileSelect = (filePath) => {
if (!filePath) return
// Convert file path to URL slug
const slug = pathToSlug(filePath)
router.push(
/doc/${slug})
} if (!mounted) return null
return (
siteConfig={wikiConfig}
initialFile={filePath}
showNavigation={true}
showTableOfContents={true}
onFileSelect={handleFileSelect}
/>
)
}
`$3
Copy your Hillnote workspace to your Next.js public folder:
`bash
cp -r ~/path-to-hillnote-workspace public/workspace
`$3
1. Update your
globals.css with theme variables:`css
/ app/globals.css /
@import "tailwindcss";:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
}
body {
background: hsl(var(--background));
color: hsl(var(--foreground));
}
`2. Create or update your
tailwind.config.js:`js
// tailwind.config.js
module.exports = {
darkMode: ["class"],
content: [
// ... your other content paths
"./node_modules/@hillnote/wiki/dist/*/.{js,mjs}"
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [],
}
`Components
$3
The navigation bar component for your wiki.
`jsx
import { Navbar } from '@hillnote/wiki' siteName="My Wiki"
showSiteName={true}
showThemeToggle={true}
className="custom-navbar"
>
{/ Additional navbar items /}
`$3
The main document viewer with navigation sidebar and table of contents.
`jsx
import { Document } from '@hillnote/wiki' siteConfig={wikiConfig} // Pass the entire config object
initialFile="documents/index.md" // Initial file to display
showNavigation={true} // Show navigation sidebar
showTableOfContents={true} // Show table of contents
onFileSelect={(file) => console.log('Selected:', file)} // File selection handler
/>
`$3
A standalone table of contents component.
`jsx
import { TableOfContents } from '@hillnote/wiki' showTitle={true}
title="Contents"
/>
`Configuration
The configuration object supports the following options:
`javascript
const wikiConfig = {
siteName: "My Documentation",
workspace: {
path: "/workspace/", // Path to workspace in public folder
enabled: true,
documentsFolder: "documents", // Folder containing markdown files
registryFile: "documents-registry.json", // Registry file name
initialFile: "documents/Start Here.md" // Initial document to display
},
ui: {
authorsNotes: true, // Enable author's notes section
navigationText: "All Pages", // Navigation sidebar title
navigationMode: "wiki" // "emoji" or "wiki" (accordion style)
}
}
`$3
The sidebar navigation supports two different display styles. You can switch between them using the
navigationMode setting in your configuration.#### 1. Emoji Mode (Default)
Traditional file explorer style with emoji icons and arrow indicators.
Features:
- š Folder icons for directories
- š File icons for documents
- ā¶ļø / ā¼ Arrow indicators for expand/collapse
- Visual hierarchy with indentation
`javascript
const wikiConfig = {
ui: {
navigationMode: "emoji" // or omit for default
}
}
`#### 2. Wiki Mode
Clean, modern accordion-style navigation similar to popular documentation sites.
Features:
- Clean text-based navigation
- Smooth accordion expand/collapse animations
- No visual clutter from icons
- Folders stay open when selecting files inside them
- Auto-expands to show the current document
`javascript
const wikiConfig = {
ui: {
navigationMode: "wiki"
}
}
`Complete example with wiki mode:
`javascript
const wikiConfig = {
siteName: "My Documentation",
workspace: {
path: "/workspace/",
enabled: true,
documentsFolder: "documents",
registryFile: "documents-registry.json",
initialFile: "documents/Start Here.md"
},
ui: {
authorsNotes: true,
navigationText: "All Pages",
navigationMode: "wiki" // Set to "wiki" for accordion style
}
}
`Advanced Usage
$3
You can compose the components to create custom layouts:
`jsx
import { useState } from 'react'
import { Navbar, Document, MarkdownRenderer, NavigationSidebar } from '@hillnote/wiki'export default function CustomWiki() {
const [selectedFile, setSelectedFile] = useState(null)
return (
onFileSelect={setSelectedFile}
selectedFile={selectedFile}
/>
filePath={selectedFile}
onFileSelect={setSelectedFile}
/>
)
}
`$3
The package includes utilities for converting between file paths and URL-friendly slugs:
`jsx
import { pathToSlug, slugToPath, initializeSlugMapping } from '@hillnote/wiki'// Initialize slug mapping (required once on app mount)
await initializeSlugMapping(wikiConfig)
// Convert file path to URL slug
const slug = pathToSlug("documents/Getting Started.md")
// Returns: "getting-started"
// Convert slug back to file path
const path = slugToPath("getting-started")
// Returns: "documents/Getting Started.md"
`
$3
The package exports utilities for working with your Hillnote workspace:
`jsx
import { buildFileTree, fetchWorkspaceRegistry, getWorkspaceFileTree } from '@hillnote/wiki'// Fetch the workspace registry
const registry = await fetchWorkspaceRegistry(wikiConfig)
// Build a hierarchical file tree from the registry
const fileTree = buildFileTree(registry.documents)
// Get the complete workspace file tree
const tree = await getWorkspaceFileTree(wikiConfig)
`Troubleshooting
$3
If you encounter hydration errors with Next.js, ensure you're using the client-side mounting pattern shown in the Quick Start example. The
mounted state prevents theme-related attributes from causing mismatches between server and client rendering.Workspace Structure
Your Hillnote workspace should have this structure in your public folder:
`
public/
āāā workspace/
āāā documents/
ā āāā Getting Started.md
ā āāā Installation.md
ā āāā ...
āāā documents-registry.json
`The
documents-registry.json file should contain metadata about your documents:`json
{
"documents": [
{
"path": "documents/Getting Started.md",
"title": "Getting Started",
"lastModified": "2024-01-01T00:00:00Z"
}
]
}
`JavaScript/TypeScript Support
The package works with both JavaScript and TypeScript. For TypeScript users, types are available:
`tsx
import type {
NavbarProps,
DocumentProps,
TableOfContentsProps,
HillnoteWikiConfig
} from '@hillnote/wiki'
`For JavaScript users, you can use JSDoc comments for type hints:
`javascript
/* @type {import('@hillnote/wiki').HillnoteWikiConfig} /
const wikiConfig = {
// your configuration
}
``- Next.js 13+ (with App Router)
- React 18+
- Tailwind CSS (for styling)
- A Hillnote workspace
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please visit our GitHub repository.