Hillnote Logo
Documentshillnote-npm

@hillnote/wiki

Turn your Hillnote workspace into beautiful documentation for your Next.js projects. The @hillnote/wiki NPM package provides ready-to-use components for creating documentation sites powered by your Hillnote workspace.

Github

You can check out the repository here for boilerplates, examples and more: https://github.com/Rajathbail/hillnote-wiki?tab=readme-ov-file

Features

  • 📚 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

Installation

npm install @hillnote/wiki
# or
yarn add @hillnote/wiki
# or
pnpm add @hillnote/wiki

Note: All required dependencies (@radix-ui components, gray-matter, etc.) will be automatically installed with the package.

Quick Start

1. Setup Root Layout with Theme Provider

First, create a providers component and update your root layout:

// app/providers.js
"use client"

import { ThemeProvider } from '@hillnote/wiki'

export function Providers({ children }) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  )
}
// app/layout.js
import { Providers } from "./providers"
import "@hillnote/wiki/styles"
import "./globals.css"

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

2. Create Your Wiki Pages

Create the main wiki page and dynamic routing:

// 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 (
    <ConfigProvider config={wikiConfig}>
      <div className="h-screen flex flex-col">
        <Navbar siteName={wikiConfig.siteName} showThemeToggle={true} />
        <Document 
          siteConfig={wikiConfig}
          initialFile={wikiConfig.workspace.initialFile}
          showNavigation={true}
          showTableOfContents={true}
          onFileSelect={handleFileSelect}
        />
      </div>
    </ConfigProvider>
  )
}
// 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 (
    <ConfigProvider config={wikiConfig}>
      <div className="h-screen flex flex-col">
        <Navbar siteName={wikiConfig.siteName} showThemeToggle={true} />
        <Document 
          siteConfig={wikiConfig}
          initialFile={filePath}
          showNavigation={true}
          showTableOfContents={true}
          onFileSelect={handleFileSelect}
        />
      </div>
    </ConfigProvider>
  )
}

3. Copy Your Hillnote Workspace

Copy your Hillnote workspace to your Next.js public folder:

cp -r ~/path-to-hillnote-workspace public/workspace

4. Setup Styles and Tailwind

  1. Update your globals.css with theme variables:
/* 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));
}
  1. Create or update your tailwind.config.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

Navbar

The navigation bar component for your wiki.

import { Navbar } from '@hillnote/wiki'

<Navbar 
  siteName="My Wiki"
  showSiteName={true}
  showThemeToggle={true}
  className="custom-navbar"
>
  {/* Additional navbar items */}
</Navbar>

Document

The main document viewer with navigation sidebar and table of contents.

import { Document } from '@hillnote/wiki'

<Document 
  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
/>

TableOfContents

A standalone table of contents component.

import { TableOfContents } from '@hillnote/wiki'

<TableOfContents 
  showTitle={true}
  title="Contents"
/>

Configuration

The configuration object supports the following options:

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)
  }
}

Navigation Modes

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

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

const wikiConfig = {
  ui: {
    navigationMode: "wiki"
  }
}

Complete example with wiki mode:

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

Custom Layout

You can compose the components to create custom layouts:

import { useState } from 'react'
import { Navbar, Document, MarkdownRenderer, NavigationSidebar } from '@hillnote/wiki'

export default function CustomWiki() {
  const [selectedFile, setSelectedFile] = useState(null)
  
  return (
    <div className="custom-layout">
      <Navbar siteName="My Wiki" />
      
      <div className="flex">
        <NavigationSidebar 
          onFileSelect={setSelectedFile}
          selectedFile={selectedFile}
        />
        
        <main className="flex-1">
          <MarkdownRenderer 
            filePath={selectedFile}
            onFileSelect={setSelectedFile}
          />
        </main>
      </div>
    </div>
  )
}

URL Routing with Slugs

The package includes utilities for converting between file paths and URL-friendly slugs:

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"

Workspace Utilities

The package exports utilities for working with your Hillnote workspace:

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

Hydration Errors

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:

{
  "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:

import type { 
  NavbarProps, 
  DocumentProps, 
  TableOfContentsProps,
  HillnoteWikiConfig 
} from '@hillnote/wiki'

For JavaScript users, you can use JSDoc comments for type hints:

/** @type {import('@hillnote/wiki').HillnoteWikiConfig} */
const wikiConfig = {
  // your configuration
}

Requirements

  • Next.js 13+ (with App Router)

  • React 18+

  • Tailwind CSS (for styling)

  • A Hillnote workspace

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please visit our GitHub repository.

This documentation was built using a hillnote workspace and @hillnote/publish | GitHub