Backend-free, SSG-free Vue knowledge base & static blog, designed for GitHub Pages.
What makes it different
• Build-time generates the notes index, rendered HTML, and raw sources
• Browser publishing via GitHub API (smart Fork + PR fallback)
• Runtime theming (CSS variables + Tailwind) with system/time-based dark mode
• Learning Lab: an 8-stage roadmap plus an annotated Source Viewer
- ✨ Features Overview
- 🧠 How Backend-free Works
- 🙏 Credits
- 🎯 Quick Start
- 🧪 Testing
- ⚡ First-Load Performance (No Cache)
- 📁 Project Structure
- 🏗️ Technical Architecture
- 🔧 Configuration Guide
- Security & Data
- 🤝 Contribution Guide
- 📜 Changelog
- 🔬 Implementation Deep Dive
- 🧩 Trade-offs for Static Deployment
- 🚀 Migrating to a Full-Stack App (No Fork+PR Dependency)
| Feature | Description |
|---|---|
| Build-time data pipeline | Generates public/data/files.json, pre-rendered HTML, raw source copies, and media manifests before deployment. |
| Runtime = static fetch | Notes and generated artifacts are loaded via fetch() from static hosting (GitHub Pages friendly). |
| Local-first state | Preferences, reading actions, and Lab progress persist in the browser (localStorage + Pinia). |
| Optional integrations | Comments (Giscus), reading stats (Umami Share Token API), and cloud backup (GitHub) without running your own backend. |
| Feature | Description |
|---|---|
| Publishing workbench | Import/preview files, rename, choose folder, inject tags/author metadata, and publish from the browser. |
| Real-time preview | Live Markdown preview while editing. |
| Auto image upload | Local images referenced in Markdown can be uploaded to GitHub automatically. |
| Smart Fork + PR | Contributors without write access can fork and open a PR automatically (upstream stays protected). |
| Feature | Description |
|---|---|
| Runtime palette switching | Theme colors are CSS variables, consumed by Tailwind for a real “change-the-whole-skin” experience. |
| Dark mode strategies | Manual toggle, follow system, or auto-switch by time schedule. |
| Wallpaper & ambience | Wallpaper layers + music player; visual ambience is part of the reading experience. |
| Sakura interaction layer | Draggable petals and long-press vortex interaction, designed not to block page clicks. |
| Feature | Description |
|---|---|
| Touch-native interactions | Uses pointer events for both mouse and touch; drag interactions disable default touch actions when needed. |
| Effects that don’t block reading | Background effects default to non-interactive layers; only petals are pointer-enabled. |
| Mobile-safe links & assets | Resolves internal links/images/PDF embeds with a GitHub Pages-friendly base URL strategy. |
| Modal & reader ergonomics | Search and panels use viewport-based heights and responsive layout to stay usable on small screens. |
| Feature | Description |
|---|---|
| 8-stage roadmap | A staged learning path (from Web basics to Vue 3 advanced and a final challenge). |
| Progress persistence | Tracks completion and recommends what to learn next, stored locally. |
| Annotated source viewer | Read real project files with preset notes and your own per-line notes. |
| Productive reading tools | Folding for functions/classes/Vue SFC blocks, plus import/export and GitHub submission for notes. |
| Feature | Description |
|---|---|
| Markdown + ToC + highlighting | Markdown rendering with automatic ToC and syntax highlighting. |
| Full-text search | MiniSearch-based search with mixed CN/EN tokenization, incremental indexing, and cached index. |
| Reader utilities | Favorites, likes, tag filtering, and reading history. |
| PDF friendly | PDF links can be embedded for in-page reading and open/download actions. |
| Feature | Description |
|---|---|
| Encrypted token storage | AES-256-GCM with PBKDF2-derived device-bound key; tokens are never stored in plaintext. |
| Backup exclusion | Tokens are explicitly excluded from all backup flows. |
| Local & cloud backups | Backup/restore preferences and progress locally, or to GitHub (optional). |
Sakura Notes runs as a static app by moving “dynamic needs” to build time and keeping user data local.
notes/ (Markdown knowledge base)
│
├─ build prep (scripts/*)
│ ├─ scan notes → public/data/files.json
│ ├─ copy notes → public/notes/**
│ ├─ pre-render → public/rendered/** (HTML + ToC)
│ └─ export source → public/raw/** (for Source Viewer)
│
└─ vite build → dist/
│
▼
GitHub Pages (static hosting)
│
▼
Browser runtime
├─ fetch() static assets (notes/index/rendered/raw)
├─ persist state in localStorage (preferences/progress)
└─ write operations (optional): GitHub API (token) + Fork+PR fallback
- Comments and reading stats implementation is inspired by RyuChan.
- Fork the Repository
- Click the
Forkbutton in the top right corner.
- Click the
- Enable GitHub Pages
- Go to
Settings→Pages. - Set "Source" to
GitHub Actions.
- Go to
- Add Content
- Create
.mdfiles innotes/zh/ornotes/en/. - Push your code; GitHub Actions will automatically build and deploy.
- Create
- Access Your Site
https://<your-username>.github.io/<repo-name>/
# Clone the repository
git clone https://github.com/soft-zihan/SakuraBlog.git
cd SakuraBlog
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run buildVisit Sakura Notes directly and click the Settings icon:
- Enter your GitHub Token (requires
reposcope). - Configure your target repository info.
- Use the "Write" workbench to compose and publish; the system will automatically fork to your account.
- A Pull Request will be created for you automatically.
npm run test -- --runThis project targets static hosting (e.g. GitHub Pages), where first-time visitors have zero cache and every byte matters. The optimization goal is: show a complete app shell fast, then progressively fill content, while pre-warming heavy modules in idle time so users rarely feel “lazy-load delays”.
- Stage 0 — HTML boot screen:
src/index.htmlrenders an immediate placeholder and runs a tiny boot script:- Shows a staged loading message (download core → init UI → load index).
- Fails fast with a helpful UI (no more infinite spinner) if the entry script fails or if boot takes too long.
- Provides “Lite Mode” (
localStorage: sakura:liteMode:v1) for slow devices/networks.
- Stage 1 — Vue mount:
src/main.tsmounts the app as soon as the entry bundle executes. - Stage 2 — Index first, content later:
src/composables/useAppInit.tsloadspublic/data/files.json:- Sidebar/tree/list can appear first.
- Article content can load afterwards (with an in-app loading overlay).
- Stage 3 — Opportunistic warm-up:
src/App.vueuses idle time to progressivelyimport()heavier modules (search/markdown/lab/write/download, etc.), so users usually don’t notice module loading.
- Remove critical-path blockers: avoid extra synchronous/defer scripts before the entry module; keep the boot path minimal.
- Async split, but not “wait until clicked”: make heavy features loadable as chunks, then preload them during idle windows (“see gaps, fill gaps”).
- Parallelize index download: the boot script can prefetch
./data/files.jsonearly, and the app init reuses it when available. - Warm up article essentials early (mobile-first): prewarm
ArticleReaderchunk + markdown/highlight deps in idle time, so the first article open doesn’t stall on dynamic imports. - Avoid bandwidth contention: delay non-critical large fetches (e.g. welcome poem data) until after the index is ready and the user is not already opening an article.
- Lite Mode escape hatch: if boot is slow, users can switch to a reduced-effects mode that prioritizes readability and responsiveness.
- Prevent layout shift on mobile: keep the article page layout stable while the header hides/shows (use transform-based hide and overlay header), so the content doesn’t “jump” during scroll.
If you keep seeing the HTML boot screen, Vue likely never mounted:
- Wrong publish directory: GitHub Pages should serve
dist/(notsrc/). - Entry bundle 404: check DevTools → Network for
assets/index-*.js. - Sub-path deployment mismatch: ensure the entry script path works under
/<repo>/on GitHub Pages. - Runtime error before mount: check Console for an exception during module initialization.
sakura-notes/
├── 📁 scripts/ # Build prep scripts (generate data into public/)
│ ├── generate-tree.ts
│ ├── generate-raw.ts
│ ├── generate-music.ts
│ └── generate-wallpapers.ts
├── 📁 src/ # Source code
│ ├── 📄 main.ts # Vue app mount entry
│ ├── 📄 App.vue # Root component (Layout & State)
│ ├── 📄 index.html # HTML entry
│ ├── 📄 constants.ts # i18n constants
│ ├── 📄 types.ts # Global type definitions
│ │
│ ├── 📁 views/ # Page Views
│ │ └── ArticleReader.vue # Dedicated Article Reader & TOC
│ │
│ ├── 📁 layout/ # Layout shells
│ │ └── AppLayout.vue
│ │
│ ├── 📁 components/ # Vue components
│ │ ├── AppHeader.vue # Top navigation bar
│ │ ├── ArticleInfoBar.vue # Article info / actions bar
│ │ ├── ArticleToc.vue # Article table of contents (TOC)
│ │ ├── ThemePanel.vue # Theme settings panel
│ │ ├── AppSidebar.vue # Sidebar navigation
│ │ ├── SidebarFilterPanel.vue # Sidebar filters
│ │ ├── MainContent.vue # Main content area
│ │ ├── 📁 Modals/ # Modal components
│ │ │ ├── CodeModal.vue
│ │ │ └── Lightbox.vue
│ │ └── 📁 lab/ # Learning Lab system
│ │ ├── LabDashboard.vue
│ │ ├── SourceCodeViewer.vue
│ │ ├── 📁 stage1-foundation/ ... 📁 stage8-challenge/
│ │ └── 📁 stages/ # Lab stage wrapper pages
│ │ └── StageLearningGuide.vue
│ │
│ ├── 📁 composables/ # Vue 3 Composables (Logic Reuse)
│ │ ├── useArticleMeta.ts # Metadata extraction
│ │ ├── useContentRenderer.ts # Markdown rendering
│ │ ├── useFile.ts # File operations
│ │ ├── useUmamiViewStats.ts # Umami Share Token stats
│ │ └── ...
│ │
│ └── 📁 stores/ # Pinia state management
│ ├── appStore.ts # Global app settings
│ ├── articleStore.ts # Article interactions
│ ├── articleNavStore.ts # Article TOC & navigation
│ ├── learningStore.ts # Learning progress
│ └── musicStore.ts # Music player state
│
│ ├── 📁 locales/ # i18n locales
│ │ ├── en.ts
│ │ └── zh.ts
│ │
│ ├── 📁 styles/ # Global styles
│ │ └── app.css
│ │
│ ├── 📁 utils/ # Utility functions
│ │ ├── fileUtils.ts
│ │ ├── i18nText.ts
│ │ ├── sanitize.ts
│ │ ├── storage.ts
│ │ └── wallhavenApi.ts
│
├── 📁 public/ # Static assets (generated data)
│ ├── 📁 data/
│ │ ├── files.json
│ │ ├── music.json
│ │ ├── wallpapers.json
│ │ ├── 📁 source-notes-preset/ # Per-file notes preset (Source Viewer)
│ │ │ ├── 📁 zh/
│ │ │ └── 📁 en/
│ │ ├── source-notes-preset.zh.json
│ │ └── source-notes-preset.en.json
│ ├── 📁 raw/ # Generated raw source files for Source Viewer
│ └── 📁 notes/
│
├── 📄 vite.config.ts # Vite build configuration
└── 📄 tsconfig.json # TypeScript configuration
| Tech | Version | Purpose |
|---|---|---|
| Vue 3 | 3.5 | Frontend framework (Composition API) |
| TypeScript | 5.4 | Type safety |
| Vite | 4.4 | Build tool |
| Pinia | 3.0 | State management |
| Tailwind CSS | 3.x | Utility-first CSS |
| Highlight.js | 11.9 | Code syntax highlighting |
| MiniSearch | 7.1 | Full-text search engine |
| Marked | 12.0 | Markdown parsing |
npm run build
# This triggers:
# 1. scripts/generate-tree.ts → Mirrors notes/, generates files.json, and exports source files into /public/raw
# 2. scripts/generate-raw.ts → Legacy raw exporter (kept for compatibility; generate-tree already exports raw files)
# 3. scripts/generate-music.ts → Scans public/music/ for music.json
# 4. scripts/generate-wallpapers.ts → Scans wallpapers to create wallpapers.json
# 5. vite build → Bundles the Vue applicationThe publishing feature requires a Personal Access Token:
- Go to GitHub Settings → Developer settings → Personal access tokens.
- Create a token with the
reposcope. - Enter the token in the site's Settings panel.
⚠️ The token is stored using AES-256-GCM encryption with a key derived from your browser fingerprint.
- Enable GitHub Discussions in your repo.
- Install the Giscus App.
- Get your configuration parameters from giscus.app.
- Update the configuration in
components/GiscusComments.vue.
Inspired by RyuChan, Sakura Notes uses Umami as the source of truth: the tracker records pageviews, and the UI reads stats via the Share Token API. Since this project uses static hosting + query navigation, each article is mapped to a virtual path /notes/<filePath> for per-article stats.
-
Create a website in Umami Cloud.
-
Copy the
websiteIdfrom the tracking script snippet in Umami. -
Enable sharing for that website, then copy the
shareIdfrom the Share URL (the last path segment). -
Create
.env(or.env.local) and set:VITE_UMAMI_SCRIPT_URL=https://cloud.umami.is/script.js VITE_UMAMI_WEBSITE_ID=<your-website-id> VITE_UMAMI_BASE_URL=https://cloud.umami.is VITE_UMAMI_SHARE_ID=<your-share-id> VITE_UMAMI_TIMEZONE=Asia/Shanghai
-
Rebuild and deploy.
When deployed as a fully static site (GitHub Pages), some media sources cannot be fetched or downloaded directly due to CORS and anti-leech policies. Sakura Notes supports an optional Cloudflare Worker proxy for:
- Bilibili search/playback via signed WBI endpoints
- Reliable downloads (music cover images, wallpapers) via
Content-Disposition: attachment
-
Deploy the Worker in
bili-proxy-worker/. -
Create
.env(or.env.local) and set:# Required for Bilibili search/playback VITE_BILI_PROXY_BASE_URL=https://<your-worker>.workers.dev # Optional: download proxy for wallpapers/covers (defaults to VITE_BILI_PROXY_BASE_URL if unset) VITE_WALLPAPER_PROXY_BASE_URL=https://<your-worker>.workers.dev
-
Rebuild and deploy.
Notes
- Bilibili search results support click-to-play and infinite scroll pagination in the UI.
- Music downloads will download audio + cover (as a separate image file) when a cover URL exists.
- The Worker exposes download-friendly routes like
GET /api/wallpaper/file?url=...&filename=....
- AES-256-GCM: Industry-standard symmetric encryption.
- Device-bound Key: The key is derived from browser/device traits (userAgent, screen, timezone, language, etc.) plus a per-token salt.
- Backup Exclusion: Tokens are strictly excluded from all backup operations.
- Direct API Use: Tokens are only sent to the GitHub API, never to any third-party servers.
| Data Type | Key Prefix | Description |
|---|---|---|
| Preferences | app-store |
Themes, fonts, wallpaper settings |
| Interactions | article-store |
Favorites, likes |
| Repo Config | github_* |
Repository info, author name |
| Backups | backup_* |
Backup data (import/export) |
| Token | github_pat_* |
Encrypted token materials (excluded from backups) |
⚠️ Warning: Clearing browser data will delete these settings. Please use the backup feature regularly!
Method 1: Web Interface
- Visit the live site and configure your Token.
- Use the Workbench to write and publish.
- The system will create a PR for you automatically.
Method 2: Direct PR
- Fork the repo.
- Add
.mdfiles to thenotes/directory. - Submit a PR to the
mainbranch.
feat: New featurefix: Bug fixdocs: Documentation updaterefactor: Code refactoringchore: Build/tool changes
- 🐛 Fixed 404 error for non-owner submissions.
- 🔧 Optimized permission checks (Smart Fork + PR logic).
- 🔐 Added AES-256-GCM encryption for Tokens.
- 💾 Added local backup system (Import/Export).
- 👁️ Added real-time preview to the Publishing Workbench.
- 🧪 Completed 8-stage Learning Lab path.
- 🌸 Added draggable Sakura petal system.
- 🔍 Integrated full-text search.
- 🎉 Initial release.
- 📝 Basic Markdown note system.
- 🌐 Multi-language support.
- 📱 Responsive design.
This section explains the critical paths that make the project work as a fully static site: what gets generated at build time, what is loaded at runtime, and why the design is structured this way.
- Goal: Let the frontend quickly know what files/folders exist and their metadata, without a backend.
- How: A build-prep script scans
notes/, generates an index JSON, and mirrors Markdown files intopublic/notes/so the browser canfetch()them. - Key script:
scripts/generate-tree.ts - Outputs:
public/data/files.json,public/notes/**
- Goal: Display this project’s own Vue components / utilities as readable source code in the Learning Lab, with “preset notes” that guide the reader.
- How:
- A build-prep script generates a “Project Source Code” tree and exports source files into
public/raw/(so production doesn’t need direct access tosrc/). - The frontend loads these raw text files and overlays preset notes (and user notes) for navigation and explanations.
- A build-prep script generates a “Project Source Code” tree and exports source files into
- Key script:
scripts/generate-tree.ts - Key component:
src/components/lab/SourceCodeViewer.vue - Preset notes data:
public/data/source-notes-preset.zh.jsonandpublic/data/source-notes-preset.en.json(optionalmatch/matchRegexanchors reduce drift when code changes) - Note:
src/utils/i18nText.tsis not the UI i18n dictionary (that lives insrc/locales/*). It only helps anchoring preset notes to code lines.
- Goal: Render Markdown client-side with a Table of Contents, syntax highlighting, and safe HTML.
- How: Parse Markdown into HTML, generate ToC, and sanitize output to reduce injection risks.
- Relevant modules:
src/composables/useContentRenderer.ts,src/utils/sanitize.ts
- Goal: Provide fast in-browser search with hit highlighting, without server-side search.
- How: Use MiniSearch on the client, combined with metadata and loading strategies to keep initial load reasonable.
- Relevant modules:
src/composables/useSearch.ts,src/stores/articleStore.ts
- Goal: Publish notes directly from the browser, including image uploads, without running your own backend.
- How: Use GitHub APIs for write operations; for users without write access, fall back to Fork + PR so contributions still work.
- Relevant module:
src/composables/useGitHubPublish.ts
- Goal: Avoid storing GitHub Tokens in plaintext and never include them in backups.
- How: AES-256-GCM encryption with keys derived from a browser fingerprint; backup flow explicitly excludes tokens.
- Relevant modules:
src/composables/useTokenSecurity.ts,src/composables/useBackup.ts
To run on GitHub Pages (or any static hosting) with no backend, the project makes these trade-offs:
- Make runtime data a build artifact: notes index, raw source copies, music/wallpaper lists are generated into
public/during build time. - Outsource write operations to third-party APIs: publishing/image uploads/cloud backup rely on GitHub APIs (authorized by a token).
- Use PR as a collaboration boundary: when a user lacks write access, Fork + PR enables contribution while protecting the upstream repo.
- Key limitations:
- Tokens exist in the browser; encryption helps, but it is not the same security posture as a server-side secret.
- GitHub APIs have rate limits and permission boundaries; PR-based publishing is not instantly live (merge required).
- Upload size/latency/availability are bounded by the GitHub ecosystem.
MIT License © 2024-present
🌸 May this project become your little garden for organizing knowledge and sharing your life 🌸
Made with ❤️ by soft-zihan
