|
1 | 1 | # Nick Liffen's Blog |
2 | 2 |
|
3 | 3 | [](https://app.netlify.com/sites/youthful-perlman-f4fbb0/deploys) |
| 4 | + |
| 5 | +A personal blog built with a custom static site generator using TypeScript, EJS templates, and Markdown content. |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +- **Node.js 22+** (required - see `engines` in package.json) |
| 10 | +- npm (comes with Node.js) |
| 11 | + |
| 12 | +## Getting Started |
| 13 | + |
| 14 | +### 1. Clone and Install |
| 15 | + |
| 16 | +```bash |
| 17 | +git clone https://github.com/NickLiffen/nickliffen-me.git |
| 18 | +cd nickliffen-me |
| 19 | +npm install |
| 20 | +``` |
| 21 | + |
| 22 | +### 2. Verify Setup |
| 23 | + |
| 24 | +```bash |
| 25 | +# Run TypeScript type checking |
| 26 | +npm run typecheck |
| 27 | + |
| 28 | +# Run tests |
| 29 | +npm test |
| 30 | + |
| 31 | +# Build the site |
| 32 | +npm run build |
| 33 | + |
| 34 | +# Validate the output |
| 35 | +npm run validate |
| 36 | +``` |
| 37 | + |
| 38 | +If all commands pass, you're ready to go! |
| 39 | + |
| 40 | +## Project Structure |
| 41 | + |
| 42 | +``` |
| 43 | +├── content/ # Markdown articles with frontmatter |
| 44 | +├── src/ |
| 45 | +│ └── templates/ # EJS templates for HTML generation |
| 46 | +├── scripts/ |
| 47 | +│ ├── build.ts # Main build script |
| 48 | +│ ├── new-article.ts # CLI to create new articles |
| 49 | +│ └── lib/ # Modular TypeScript utilities |
| 50 | +│ ├── articles.ts # Article loading and processing |
| 51 | +│ ├── assets.ts # Static file copying |
| 52 | +│ ├── config.ts # Site configuration |
| 53 | +│ ├── dates.ts # Date formatting utilities |
| 54 | +│ ├── templates.ts # EJS template rendering |
| 55 | +│ ├── types.ts # TypeScript interfaces |
| 56 | +│ └── generators/ # Output generators |
| 57 | +│ ├── feeds.ts # Sitemap and RSS generation |
| 58 | +│ ├── pages.ts # HTML page generation |
| 59 | +│ └── pwa.ts # PWA manifest and service worker |
| 60 | +├── dist/ # Built output (generated) |
| 61 | +├── css/ # Stylesheets (copied to dist) |
| 62 | +├── img/ # Images (copied to dist) |
| 63 | +└── coverage/ # Test coverage reports (generated) |
| 64 | +``` |
| 65 | + |
| 66 | +## Available Scripts |
| 67 | + |
| 68 | +| Command | Description | |
| 69 | +|---------|-------------| |
| 70 | +| `npm run build` | Build the production site to `dist/` | |
| 71 | +| `npm run build:dev` | Build with draft articles included | |
| 72 | +| `npm run dev` | Build (with drafts) and serve locally | |
| 73 | +| `npm run validate` | Validate the built output | |
| 74 | +| `npm run new-article "Title"` | Create a new article with frontmatter template | |
| 75 | +| `npm run typecheck` | Run TypeScript type checking | |
| 76 | +| `npm test` | Run unit tests | |
| 77 | +| `npm run test:watch` | Run tests in watch mode | |
| 78 | +| `npm run test:coverage` | Run tests with coverage report | |
| 79 | +| `npm run clean` | Remove the `dist/` directory | |
| 80 | + |
| 81 | +## Publishing a New Article |
| 82 | + |
| 83 | +### Step 1: Create the Article |
| 84 | + |
| 85 | +```bash |
| 86 | +npm run new-article "Your Article Title" |
| 87 | +``` |
| 88 | + |
| 89 | +This creates a new Markdown file in `content/` with frontmatter template: |
| 90 | + |
| 91 | +```markdown |
| 92 | +--- |
| 93 | +slug: "your-article-title" |
| 94 | +title: "Nick Liffen's Blog | Your Article Title | Blog Post" |
| 95 | +headline: "Your Article Title" |
| 96 | +description: "Nick Liffen's Blog | Your Article Title | Blog Post" |
| 97 | +date: "2024-12-22" |
| 98 | +modified: "2024-12-22" |
| 99 | +keywords: |
| 100 | + - Nick |
| 101 | + - Liffen |
| 102 | + - Blog |
| 103 | +image: "https://nickliffen.dev/img/tech.jpg" |
| 104 | +sections: |
| 105 | + - Introduction |
| 106 | + - Conclusion |
| 107 | +articleBody: "Add a brief excerpt here..." |
| 108 | +hasYouTube: false |
| 109 | +draft: true |
| 110 | +--- |
| 111 | + |
| 112 | +## Introduction |
| 113 | + |
| 114 | +Start writing your article here... |
| 115 | +``` |
| 116 | + |
| 117 | +### Step 2: Write Your Content |
| 118 | + |
| 119 | +Edit the generated file in `content/`: |
| 120 | +1. Update the frontmatter (title, description, keywords, image, sections) |
| 121 | +2. Write your article content in Markdown |
| 122 | +3. The `articleBody` field is used for RSS feed excerpts |
| 123 | + |
| 124 | +### Step 3: Preview with Drafts |
| 125 | + |
| 126 | +```bash |
| 127 | +npm run dev |
| 128 | +``` |
| 129 | + |
| 130 | +This builds with `draft: true` articles included and serves at `http://localhost:3000`. |
| 131 | + |
| 132 | +### Step 4: Publish |
| 133 | + |
| 134 | +When ready to publish: |
| 135 | +1. Set `draft: false` in the frontmatter |
| 136 | +2. Update the `modified` date if needed |
| 137 | +3. Commit and push your changes |
| 138 | + |
| 139 | +```bash |
| 140 | +git add content/your-article-title.md |
| 141 | +git commit -m "Add article: Your Article Title" |
| 142 | +git push |
| 143 | +``` |
| 144 | + |
| 145 | +The CI/CD pipeline will automatically build, test, and deploy to Netlify. |
| 146 | + |
| 147 | +## Development Workflow |
| 148 | + |
| 149 | +### Running Tests |
| 150 | + |
| 151 | +```bash |
| 152 | +# Run all tests once |
| 153 | +npm test |
| 154 | + |
| 155 | +# Run tests in watch mode (re-runs on file changes) |
| 156 | +npm run test:watch |
| 157 | + |
| 158 | +# Run tests with coverage report |
| 159 | +npm run test:coverage |
| 160 | +``` |
| 161 | + |
| 162 | +Coverage reports are generated in the `coverage/` directory. The project maintains **90%+ code coverage**. |
| 163 | + |
| 164 | +### Type Checking |
| 165 | + |
| 166 | +```bash |
| 167 | +npm run typecheck |
| 168 | +``` |
| 169 | + |
| 170 | +This runs TypeScript's type checker without emitting files. |
| 171 | + |
| 172 | +### Full Build Pipeline |
| 173 | + |
| 174 | +```bash |
| 175 | +# Clean previous build |
| 176 | +npm run clean |
| 177 | + |
| 178 | +# Type check |
| 179 | +npm run typecheck |
| 180 | + |
| 181 | +# Run tests with coverage |
| 182 | +npm run test:coverage |
| 183 | + |
| 184 | +# Build production site |
| 185 | +npm run build |
| 186 | + |
| 187 | +# Validate output |
| 188 | +npm run validate |
| 189 | +``` |
| 190 | + |
| 191 | +## Article Frontmatter Reference |
| 192 | + |
| 193 | +| Field | Required | Description | |
| 194 | +|-------|----------|-------------| |
| 195 | +| `slug` | Yes | URL-safe identifier (e.g., `my-article-title`) | |
| 196 | +| `title` | Yes | Full page title for SEO | |
| 197 | +| `headline` | Yes | Article headline displayed on the page | |
| 198 | +| `description` | Yes | Meta description for SEO | |
| 199 | +| `date` | Yes | Publication date (YYYY-MM-DD) | |
| 200 | +| `modified` | No | Last modified date (YYYY-MM-DD) | |
| 201 | +| `keywords` | No | Array of keywords for SEO | |
| 202 | +| `image` | No | Open Graph image URL | |
| 203 | +| `sections` | No | Array of section headings for structured data | |
| 204 | +| `articleBody` | No | Excerpt for RSS feed | |
| 205 | +| `hasYouTube` | No | Set to `true` to enable YouTube embed styles | |
| 206 | +| `draft` | No | Set to `true` to exclude from production builds | |
| 207 | + |
| 208 | +## CI/CD Pipeline |
| 209 | + |
| 210 | +The GitHub Actions workflow (`.github/workflows/build.yml`) runs on every PR and push to main: |
| 211 | + |
| 212 | +1. **TypeScript Check** - Validates types |
| 213 | +2. **Tests with Coverage** - Runs Vitest with coverage thresholds |
| 214 | +3. **Build** - Generates the static site |
| 215 | +4. **Validate** - Checks all HTML and XML files |
| 216 | +5. **Lighthouse** - Runs performance audits (on PRs) |
| 217 | + |
| 218 | +Coverage reports are automatically posted as PR comments. |
| 219 | + |
| 220 | +## Tech Stack |
| 221 | + |
| 222 | +- **Node.js 22** - Runtime |
| 223 | +- **TypeScript** - Type-safe JavaScript |
| 224 | +- **tsx** - TypeScript execution |
| 225 | +- **EJS** - HTML templating |
| 226 | +- **Marked** - Markdown to HTML |
| 227 | +- **gray-matter** - Frontmatter parsing |
| 228 | +- **Vitest** - Testing framework |
| 229 | +- **Netlify** - Hosting and deployment |
0 commit comments