diff --git a/.github/workflows/build-deploy-site.yaml b/.github/workflows/build-deploy-site.yaml index b63e048..b921430 100644 --- a/.github/workflows/build-deploy-site.yaml +++ b/.github/workflows/build-deploy-site.yaml @@ -1,13 +1,37 @@ name: "Build and Deploy site" -on: [push] + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + jobs: - Build: + build-and-deploy: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v1 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: 16.x - - run: npm ci - - run: npm run build -- --prefix-paths + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run build + run: npm run build + env: + NODE_ENV: production + + - name: Deploy to GitHub Pages + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + cname: learning-architect.blog \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7097b40 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,109 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a technical blog called "Блог обучающегося архитектора" (Learning Architect's Blog) hosted at https://learning-architect.blog. The blog focuses on software architecture, modern development practices, AI/LLM usage in development, DevOps, and technical design patterns. + +## Tech Stack + +- **Gatsby v5.14.5** - Static site generator +- **React v18.2.0** - UI framework +- **MDX v2.3.0** - Markdown with JSX for content authoring +- **TypeScript** - For component development (.tsx files) +- **Tailwind CSS v3.4.15** - Utility-first CSS framework +- **Sass** - CSS preprocessor +- **PrismJS** - Code syntax highlighting + +## Development Commands + +```bash +npm run develop # Start development server at localhost:8000 +npm run build # Build production-ready static site +npm run serve # Serve production build locally +npm run clean # Clean Gatsby cache and public folders +``` + +## Content Structure + +Blog posts are written in MDX format and stored in `/src/pages/`. Each article must include frontmatter with: + +```mdx +--- +slug: "your-article-slug" +date: "2024-12-10" +author: "Evgeniy Moroz" +keywords: + - keyword1 + - keyword2 +--- +``` + +Article excerpts are defined using `{/* cut */}` JSX comments in the MDX content. The excerpt system works as follows: + +- **Two `{/* cut */}` markers**: Content between the first and second `{/* cut */}` becomes the excerpt +- **One `{/* cut */}` marker**: Content before the first `{/* cut */}` becomes the excerpt +- **No markers**: Falls back to Gatsby's default excerpt + +The excerpt is automatically converted from Markdown to HTML and displayed in the article summary section on the homepage. + +## Architecture + +### Key Components +- `src/components/WithSidebarLayout.tsx` - Main layout wrapper +- `src/components/DefaultPageLayout.tsx` - Layout for MDX pages +- `src/pages/index.tsx` - Homepage listing all blog posts + +### Data Flow +- GraphQL queries fetch blog post data +- Static generation at build time +- RSS feed automatically generated at `/rss.xml` +- Sitemap generated at `/sitemap.xml` + +### Styling +- Tailwind CSS v3 for utility classes with JIT mode enabled +- Global styles in `src/styles/global.css` (imported in gatsby-browser.js) +- Component styles use Tailwind utilities +- Code blocks styled with Prism twilight theme +- Custom CSS classes defined: `headline`, `secondary-h`, `sub-h`, `article`, `link-default` + +## Creating New Blog Posts + +1. Create a new `.mdx` file in `/src/pages/` +2. Add required frontmatter (slug, date, author, keywords) +3. Write content using Markdown and JSX components +4. Structure content with `{/* cut */}` markers for excerpts: + ```mdx + # Article Title + {/* cut */} + This is the excerpt content that will appear on the homepage. + {/* cut */} + Rest of the article content... + ``` +5. Run `npm run develop` to preview +6. Build with `npm run build` before deploying + +## Important Configurations + +- `gatsby-config.js` - Main Gatsby configuration +- `tailwind.config.js` - Tailwind CSS configuration +- `postcss.config.js` - PostCSS configuration for Tailwind processing +- `gatsby-browser.js` - Browser-specific imports (CSS and Prism theme) +- `gatsby-ssr.js` - Server-side rendering setup with critical CSS to prevent FOUC +- `gatsby-node.js` - Custom excerpt extraction logic for `{/* cut */}` tags +- Site metadata and RSS feed settings in gatsby-config.js + +## Deployment + +The site uses GitHub Actions for automated deployment: +- **Workflow**: `.github/workflows/build-deploy-site.yaml` +- **Node.js version**: 20.x LTS +- **Triggers**: Push to main branch, pull requests +- **Deployment**: Automatically deploys to GitHub Pages at learning-architect.blog +- **Build command**: `npm run build` (production optimized) + +### GitHub Pages Setup Required: +1. Enable GitHub Pages in repository settings +2. Set source to "GitHub Actions" +3. Ensure `GITHUB_TOKEN` has write permissions for Pages \ No newline at end of file diff --git a/gatsby-browser.js b/gatsby-browser.js index d463c6c..6adb953 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,13 @@ -import './src/styles/global.css'; -import 'prismjs/themes/prism-twilight.css'; +import React from "react" +import DefaultPageLayout from "./src/components/DefaultPageLayout" +import "./src/styles/global.css" +import "prismjs/themes/prism-twilight.css" +// Wrap all MDX pages with DefaultPageLayout +export const wrapPageElement = ({ element, props }) => { + // Check if this is an MDX page + if (props.pageContext && props.pageContext.frontmatter) { + return {element} + } + return element +} \ No newline at end of file diff --git a/gatsby-config.js b/gatsby-config.js index dc4384d..cce9929 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -4,8 +4,15 @@ module.exports = { siteUrl: `https://learning-architect.blog`, }, plugins: [ + { + resolve: "gatsby-plugin-postcss", + options: { + cssLoaderOptions: { + modules: false, + }, + }, + }, "gatsby-plugin-sass", - "gatsby-plugin-postcss", "gatsby-plugin-image", "gatsby-plugin-react-helmet", { @@ -18,6 +25,13 @@ module.exports = { { resolve: "gatsby-plugin-mdx", options: { + extensions: [`.mdx`, `.md`], + mdxOptions: { + remarkPlugins: [ + require('remark-gfm'), + ], + rehypePlugins: [], + }, gatsbyRemarkPlugins: [ { resolve: `gatsby-remark-images`, @@ -31,9 +45,6 @@ module.exports = { }, }, ], - defaultLayouts: { - default: require.resolve(`./src/components/DefaultPageLayout.tsx`), - }, } }, `gatsby-remark-images`, @@ -74,32 +85,33 @@ module.exports = { { serialize: ({query: {site, allMdx}}) => { return allMdx.nodes.map(node => { + const slug = node.internal.contentFilePath.replace(/^.*\/src\/pages\//, '').replace(/\.mdx?$/, '') + const title = slug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) return Object.assign({}, node.frontmatter, { - description: node.fields.articleCut, + description: node.fields ? node.fields.articleCut : node.excerpt, date: node.frontmatter.date, - title: node.headings[0].value, - url: `${site.siteMetadata.siteUrl}/${node.slug}/`, - guid: `${site.siteMetadata.siteUrl}/${node.slug}/` + title: title, + url: `${site.siteMetadata.siteUrl}/${slug}/`, + guid: `${site.siteMetadata.siteUrl}/${slug}/` }) }) }, query: ` { allMdx( - sort: { fields: [frontmatter___date], order: DESC } + sort: { frontmatter: { date: DESC } } ) { nodes { id - headings(depth: h1) { - value - } + excerpt + internal { + contentFilePath + } fields { articleCut } - slug frontmatter { date - title author keywords } diff --git a/gatsby-node.js b/gatsby-node.js index 5abc464..433fa6a 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,22 +1,72 @@ -const remark = require('remark') -const remarkHTML = require('remark-html') +const fs = require('fs') -const EXCERPT_SEPARATOR = '' +const EXCERPT_SEPARATOR = '{/* cut */}' + +// Simple markdown to HTML converter +function markdownToHtml(markdown) { + return markdown + .replace(/^# (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^### (.*$)/gim, '

$1

') + .replace(/\*\*(.*?)\*\*/gim, '$1') + .replace(/\*(.*?)\*/gim, '$1') + .replace(/~~(.*?)~~/gim, '$1') + .replace(/\[(.*?)\]\((.*?)\)/gim, '$1') + .replace(/`(.*?)`/gim, '$1') + .replace(/\n\n+/gim, '

') + .replace(/^(.+)$/gim, '

$1

') + .replace(/

<\/p>/gim, '') + .replace(/

(.*<\/h[1-6]>)<\/p>/gim, '$1') + .replace(/

(