diff --git a/CHANGELOG.md b/CHANGELOG.md index ac310ae..8d0ea9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (code); dark theme is now the default with a persisted light/dark toggle. - Homepage hero image swapped from a remote Midjourney-hosted asset to the self-hosted `public/demo/oss_landingpage.webp`. +- README rewritten with a hero value proposition, "Why ossium?" comparison, + grouped features, screenshots, corrected tech stack (Next.js 16, unified + remark/rehype pipeline), and a scannable quickstart; covered by a + `lib/readme.test.ts` regression suite. ## [0.1.0]- 2026-07-11 diff --git a/README.md b/README.md index 5dd9adc..4453efa 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,150 @@ -# Ossium Blog +# ossium Blog -The official blog of [Ossium](https://ossium.in) - practical guides, how-tos, and Q&A on open source contribution, GSoC, good first issues, GitHub workflows, and tools for contributors. +> From first issue to first merge — practical guides, how-tos, and Q&A that help +> developers land their first open-source contribution, crack GSoC, and ship with +> confidence. [![CI](https://img.shields.io/github/actions/workflow/status/MxCorpIn/blogs/ci.yml?branch=main&label=CI)](https://github.com/MxCorpIn/blogs/actions/workflows/ci.yml) [![Stars](https://img.shields.io/github/stars/MxCorpIn/blogs)](https://github.com/MxCorpIn/blogs) +[![Last commit](https://img.shields.io/github/last-commit/MxCorpIn/blogs)](https://github.com/MxCorpIn/blogs/commits/main) [![Version](https://img.shields.io/github/package-json/v/MxCorpIn/blogs)](https://github.com/MxCorpIn/blogs/blob/main/package.json) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/MxCorpIn/blogs/blob/main/CONTRIBUTING.md) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/MxCorpIn/blogs/blob/main/LICENSE) -**Live demo:** [blog.ossium.in](https://blog.ossium.in) +**Live site:** [blog.ossium.in](https://blog.ossium.in) · +**Main product:** [ossium.in](https://ossium.in) · +**Contribute:** [CONTRIBUTING.md](CONTRIBUTING.md) · +**Issues:** [open an issue](https://github.com/MxCorpIn/blogs/issues) -## Overview +![ossium Blog — guides and Q&A for open-source contributors](public/demo/oss_landingpage.webp) -A fast, static-friendly blog built with Next.js (App Router) and Tailwind CSS, powered by Markdown. Posts live in `content/posts/` as plain Markdown files with YAML frontmatter- no CMS, no database. +## Table of contents + +- [Why ossium?](#why-ossium) +- [Features](#features) +- [Screenshots](#screenshots) +- [Tech stack](#tech-stack) +- [Quickstart](#quickstart) +- [Writing posts](#writing-posts) +- [Project structure](#project-structure) +- [Contributing](#contributing) +- [License](#license) +- [Support](#support) + +## Why ossium? + +Generic dev blogs teach syntax. ossium teaches **getting merged**: finding the +right issue, writing a reviewable PR, and navigating programs like GSoC and +Hacktoberfest. + +| If you use… | You get… | With ossium you get… | +| --------------------- | ------------------------------------------ | ----------------------------------------------------- | +| Medium / Dev.to posts | One-off tutorials, paywalls, mixed quality | A curated path: beginner → first PR → GSoC-ready | +| Official program docs | Complete but dense reference material | Opinionated, step-by-step playbooks with examples | +| Generic SEO roundups | “Top 10 tools” with no workflow | Workflows tied to real contributor tasks and tools | +| A from-scratch blog | Weeks of CMS, SEO, and theme setup | Markdown-first SSG with search, SEO, and RSS built in | ## Features -- **Markdown-first content** - every post is a `.md` file with YAML frontmatter -- **Static generation (SSG)** - all post pages pre-rendered at build time for fast loads and strong SEO -- **Search + filters** - client-side search across title, description, tags, and keywords, with type (`article` / `guide` / `how-to` / `Q&A`) and category filters -- **Featured rail** - a sticky "Featured" column on the listing page -- **Light/dark theme** - dark by default, with a persisted, flash-free toggle -- **SEO out of the box** - per-post metadata, JSON-LD (`Blog`, `FAQ` for `question` posts), `sitemap.xml`, `robots.txt`, and an RSS 2.0 feed -- **Reading time** - estimated from the Markdown body (~200 wpm) -- **Related posts** - ranked by category, type, and shared tags +**Write in Markdown, ship a fast site.** + +- 📝 **Markdown-first** — every post is a `.md` file with typed YAML + frontmatter. No CMS, no database. +- ⚡ **Static generation** — all pages pre-rendered at build time for fast + loads and strong SEO. +- 🔍 **Search + filters** — instant client-side search across titles, tags, + and keywords, with `article` / `guide` / `howto` / `question` and category + filters. +- ⭐ **Featured rail** — sticky spotlight column for must-read posts. +- 🌓 **Light/dark theme** — dark by default, persisted, flash-free toggle. +- 📈 **SEO out of the box** — per-post metadata, `Blog` + `FAQ` JSON-LD, + `sitemap.xml`, `robots.txt`, and an RSS 2.0 feed (`/feed.xml`). +- ⏱️ **Reading time + related posts** — ~200 wpm estimates and ranking by + category, type, and shared tags. +- 🖼️ **Galleries + code windows** — `:::gallery` directive for responsive + image grids, captioned figures, and syntax-highlighted code blocks. + +## Screenshots + +| Landing | Dashboard | +| ------------------------------------------------------ | ----------------------------------------------------------------------------- | +| ![Blog landing page](public/demo/oss_landingpage.webp) | ![Contributor dashboard](public/demo/oss_dashboardpage.webp) | +| Search, filters, and the featured rail. | Where readers put the guides into practice on [ossium.in](https://ossium.in). | + +Open Graph preview (`/demo/oss_og-toposspage.png`) powers link unfurls on +social and chat apps. ## Tech stack -| Layer | Choice | -| --------------- | ------------------------------------------------------------------------------------------------------------------- | -| Framework | [Next.js](https://nextjs.org) 15 (App Router) | -| UI | [React](https://react.dev) 19 + [Tailwind CSS](https://tailwindcss.com) v4 | -| Content | Markdown + YAML frontmatter (`content/posts/`) | -| Rendering | [react-markdown](https://github.com/remarkjs/react-markdown) + [remark-gfm](https://github.com/remarkjs/remark-gfm) | -| Fonts | Inter (body), General Sans (headings), JetBrains Mono (code) - loaded from CDN | -| Language | TypeScript (strict) | -| Package manager | pnpm | +| Layer | Choice | +| --------------- | --------------------------------------------------------------------------------------------------- | +| Framework | [Next.js](https://nextjs.org) 16 (App Router, SSG) | +| UI | [React](https://react.dev) 19 + [Tailwind CSS](https://tailwindcss.com) v4 | +| Content | Markdown + YAML frontmatter (`content/posts/`) | +| Rendering | Server-side [unified](https://unifiedjs.com) pipeline (`remark`/`rehype`) + `:::gallery` directives | +| Fonts | Inter (body), General Sans (headings), JetBrains Mono (code) via CDN | +| Language | TypeScript (strict) | +| Package manager | [pnpm](https://pnpm.io) 11.15.1 (via `packageManager` + Corepack) | + +How a post becomes a page: + +```mermaid +flowchart LR + A["content/posts/*.md"] --> B["unified: remark-parse, remark-gfm, directives"] + B --> C["rehype-highlight + post-process"] + C --> D["Static HTML at build time"] + D --> E["/:slug + sitemap + RSS"] +``` -## Getting started +## Quickstart ### Prerequisites - **Node.js 20+** (see `engines` in `package.json`) -- **pnpm 8+** - the repo pins `pnpm@11.15.1` via `packageManager`; enable it with `corepack enable` if you use Corepack +- **pnpm 11+** — `corepack enable` activates the pinned `pnpm@11.15.1` -### 1. Clone the repo +### Run it locally ```bash git clone https://github.com/MxCorpIn/blogs.git -cd ossium-blog -``` - -### 2. Install dependencies - -```bash +cd blogs pnpm install -``` - -### 3. Configure environment variables - -```bash cp .env.example .env -``` - -| Variable | Description | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `NEXT_PUBLIC_APP_URL` | Public base URL of the site. Used for canonical URLs, RSS links, sitemap, and OG image URLs. Use `http://localhost:3000` locally and your deployed domain (e.g. `https://blog.ossium.in`) in production. | - -See [.env.example](.env.example) for the complete list with inline comments. - -### 4. Start the dev server - -```bash pnpm dev ``` Open . -### 5. Build for production +| Variable | What it does | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `NEXT_PUBLIC_APP_URL` | Base URL for canonical links, sitemap, RSS, and OG images. `http://localhost:3000` locally, `https://blog.ossium.in` in production. | + +See [.env.example](.env.example) for the annotated list. + +### Build for production ```bash pnpm build && pnpm start ``` -## Folder structure +### Verify before you push -``` -ossium-blog/ -├── app/ # Next.js App Router - pages, layouts, SEO routes -│ ├── [slug]/page.tsx # Individual post pages (SSG via generateStaticParams) -│ ├── BlogListing.tsx # Client component: search, filters, featured rail -│ ├── feed.xml/route.ts # RSS 2.0 feed -│ ├── sitemap.ts # Sitemap (listing + every post) -│ ├── robots.ts # robots.txt -│ └── layout.tsx # Root layout (theme, metadata, fonts) -├── components/ # React components -│ ├── blog/ # PostCard, MarkdownBody, ShareButton -│ ├── landing/ # Footer, DashedFrame -│ ├── theme/ # ThemeToggle -│ └── ui/ # Container -├── content/posts/ # Blog posts - Markdown + YAML frontmatter -├── lib/ -│ ├── content/ # Frontmatter parser, post loading, related posts -│ ├── constants.ts # APP_URL, APP_CONFIG, SOURCE_EDIT_BASE -│ └── utils.ts # cn() classname helper -├── public/ # Static assets (logos, demo images) -├── .github/ # Issue/PR templates, CI workflow, FUNDING -└── next.config.ts # Next.js config (security headers, image caching) +```bash +pnpm lint && pnpm typecheck && pnpm format:check && pnpm test && pnpm build ``` ## Writing posts -1. Create `content/posts/your-slug.md` (the slug is the filename without `.md`). -2. Add YAML frontmatter (see [content/posts/README.md](content/posts/README.md) for the full schema). -3. Write the body in Markdown. -4. Visit `/{slug}` locally - the sitemap and RSS feed pick up published posts automatically. +1. Create `content/posts/your-slug.md` — the filename is the URL. +2. Add frontmatter (full schema: [content/posts/README.md](content/posts/README.md)). +3. Write Markdown — galleries via `:::gallery columns=3`, captions via image + alt text. +4. Preview at `/{slug}` — sitemap and RSS pick up published posts automatically. ```yaml --- title: "Clear, keyword-aware title" -description: "1–2 sentences for SEO meta and cards (~150–160 chars ideal)." +description: "1-2 sentences for SEO meta and cards (~150-160 chars ideal)." type: article | guide | howto | question category: contribution | gsoc | github | career | tools | programs | beginners tags: [tag-one, tag-two] @@ -134,23 +160,58 @@ answerSummary: "Only for type=question - short answer for FAQ rich results." --- ``` -Set `draft: true` to hide a post from production until it's ready. +Set `draft: true` to keep a post out of production until it is ready. + +## Project structure + +``` +blogs/ +├── app/ # App Router pages, layouts, SEO routes +│ ├── [slug]/page.tsx # Post pages (SSG via generateStaticParams) +│ ├── BlogListing.tsx # Search, filters, featured rail +│ ├── feed.xml/ # RSS 2.0 feed +│ ├── sitemap.ts # Sitemap (listing + every post) +│ ├── robots.ts # robots.txt +│ └── layout.tsx # Theme, metadata, fonts +├── components/blog/ # PostCard, MarkdownBody, ShareButton +├── components/landing/ # Navbar, Footer, DashedFrame +├── components/theme/ # ThemeToggle +├── content/posts/ # Markdown posts + frontmatter schema README +├── lib/content/ # Loading, parsing, related posts, types +├── lib/markdown/ # :::gallery directives + server renderer +└── public/demo/ # Landing, dashboard, and OG preview images +``` ## Contributing -Contributions are welcome - content corrections, new posts, and code improvements all count. Please read [CONTRIBUTING.md](CONTRIBUTING.md) first for branch naming, commit message format, and the PR checklist. All community interaction is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). +Code, content, and docs all count. Start with +[CONTRIBUTING.md](CONTRIBUTING.md) — branch naming, Conventional Commits, and +the PR checklist. All interaction follows the +[Code of Conduct](CODE_OF_CONDUCT.md). -Thanks to everyone who has contributed content, code, and support in issues, pull requests, and discussions: +| Way to help | Example | +| ----------- | -------------------------------------------- | +| ✍️ Content | Fix a typo, update a stale guide, add a post | +| 💻 Code | Search, SEO, performance, accessibility, DX | +| 🧭 Triage | Reproduce bugs, answer questions, review PRs | +| 📚 Docs | Clarify setup, improve this README | - - +Thanks to everyone who has contributed code, content, and reviews: + + + Contributors ## License -- **Code** (everything under `app/`, `components/`, `lib/`, and config files) - [MIT License](LICENSE) -- **Content** (everything under `content/posts/`) - Copyright © 2026 Ossium. All rights reserved. Do not republish blog posts without permission. +- **Code** (`app/`, `components/`, `lib/`, configs) — [MIT License](LICENSE) +- **Content** (`content/posts/`) — © Ossium, all rights reserved. Do not + republish posts without permission. + +See [CHANGELOG.md](CHANGELOG.md) for release history. ## Support -Questions or content corrections? Email [help@ossium.in](mailto:help@ossium.in) or open a [GitHub issue](https://github.com/MxCorpIn/blogs/issues). +- 📧 Email [help@ossium.in](mailto:help@ossium.in) +- 🐛 [Open an issue](https://github.com/MxCorpIn/blogs/issues) +- 🔒 Security issues: see [SECURITY.md](SECURITY.md) diff --git a/lib/readme.test.ts b/lib/readme.test.ts new file mode 100644 index 0000000..c2ba1d6 --- /dev/null +++ b/lib/readme.test.ts @@ -0,0 +1,101 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const REPO_ROOT = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", +); +const README_PATH = path.join(REPO_ROOT, "README.md"); + +function readReadme(): string { + return readFileSync(README_PATH, "utf8"); +} + +describe("README", () => { + it("leads with a one-liner value proposition and live links", () => { + const readme = readReadme(); + expect(readme).toMatch(/^# .+$/m); + expect(readme).toMatch(/first issue to first merge/i); + expect(readme).toContain("https://blog.ossium.in"); + expect(readme).toContain("https://ossium.in"); + }); + + it("covers every acceptance-criteria section", () => { + const readme = readReadme(); + for (const heading of [ + "## Why ossium?", + "## Features", + "## Screenshots", + "## Tech stack", + "## Quickstart", + "## Contributing", + "## License", + "## Support", + ]) { + expect(readme).toContain(heading); + } + }); + + it("shows badges for CI, stars, version, and license", () => { + const readme = readReadme(); + for (const badge of ["label=CI", "/stars/", "/package-json/v", "license"]) { + expect(readme).toContain(badge); + } + }); + + it("references screenshots that exist in the repo", () => { + const readme = readReadme(); + const images = [...readme.matchAll(/public\/demo\/[\w.\-]+\.(?:webp|png)/g)] + .map((m) => m[0]) + .filter((v, i, a) => a.indexOf(v) === i); + expect(images.length).toBeGreaterThan(0); + for (const image of images) { + expect( + existsSync(path.join(REPO_ROOT, image)), + `missing screenshot asset: ${image}`, + ).toBe(true); + } + }); + + it("documents the real stack and content model", () => { + const readme = readReadme(); + expect(readme).toMatch(/Next\.js.*16/); + expect(readme).toMatch(/unified/i); + expect(readme).toMatch(/remark/i); + expect(readme).toMatch(/rehype/i); + expect(readme).toContain("howto"); + expect(readme).toContain("question"); + expect(readme).not.toContain("Next.js](https://nextjs.org) 15"); + }); + + it("keeps setup scannable: clone, install, env, dev, build", () => { + const readme = readReadme(); + for (const step of [ + "https://github.com/MxCorpIn/blogs.git", + "pnpm install", + ".env.example", + "NEXT_PUBLIC_APP_URL", + "pnpm dev", + "http://localhost:3000", + "pnpm build", + ]) { + expect(readme).toContain(step); + } + }); + + it("only links to docs that exist", () => { + const readme = readReadme(); + const links = [...readme.matchAll(/\]\((\.[^)#]+?)(?:#[^)]*)?\)/g)].map( + (m) => m[1], + ); + expect(links.length).toBeGreaterThan(0); + for (const link of links) { + expect( + existsSync(path.join(REPO_ROOT, link)), + `broken relative link: ${link}`, + ).toBe(true); + } + }); +});