{
    "componentChunkName": "component---src-templates-blog-post-tsx",
    "path": "/gatsby-github-pages-blog-mien-phi/",
    "result": {"data":{"site":{"siteMetadata":{"title":"Chung Tran","author":"@chungtran4078"}},"markdownRemark":{"id":"19d6e1e7-9b06-5a4d-944e-c13fbc4b6871","excerpt":"Hồi mới code, mình cũng từng xài WordPress. Mất 2 tiếng cài đặt, 1 tiếng chọn theme, 3 tiếng custom CSS cho cái theme nó vừa ý, và cuối cùng nhận ra: “ủa, mình…","html":"<p>Hồi mới code, mình cũng từng xài WordPress. Mất 2 tiếng cài đặt, 1 tiếng chọn theme, 3 tiếng custom CSS cho cái theme nó vừa ý, và cuối cùng nhận ra: “ủa, mình muốn viết blog chứ có muốn làm web designer đéo đâu?“.</p>\n<p>Rồi mình chuyển qua Medium. Xài được 1 thời gian cũng ổn, cho đến khi thấy bài viết của mình bị chèn quảng cáo của Medium và khách vào đọc bị đòi đăng ký tài khoản. Cảm giác như dẫn bạn về nhà chơi mà bạn bị bảo vệ chung cư chặn lại đòi xem CCCD vậy. Đéo ổn.</p>\n<p>Thế là mình quyết định tự build blog. Tiêu chí đặt ra:</p>\n<ul>\n<li><strong>Nhanh</strong> (cả cho người viết lẫn người đọc)</li>\n<li><strong>Rẻ</strong> (càng rẻ càng tốt, free thì càng tốt)</li>\n<li><strong>Không phải maintain server</strong> (ai rảnh đâu mà suốt ngày SSH với update bảo mật)</li>\n<li><strong>Code được</strong> (vì dân IT mà, không code tay ngứa)</li>\n</ul>\n<p>Sau 1 hồi research, combo <strong>Gatsby + GitHub Pages</strong> sáng lên như vầng thái dương giữa đêm tối.</p>\n<h2>Tại sao lại là Gatsby mà không phải thứ khác?</h2>\n<p>Thị trường static site generator giờ nhiều như quân Nguyên: Jekyll (Ruby), Hugo (Go), Next.js (React), 11ty (JS), Hexo (JS), Docusaurus (React)… Tại sao mình chọn Gatsby?</p>\n<table>\n<thead>\n<tr>\n<th>Tiêu chí</th>\n<th>Jekyll</th>\n<th>Hugo</th>\n<th>Next.js</th>\n<th>Gatsby</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Ngôn ngữ</td>\n<td>Ruby</td>\n<td>Go</td>\n<td>React</td>\n<td>React</td>\n</tr>\n<tr>\n<td>Hot reload</td>\n<td>Chậm</td>\n<td>Nhanh</td>\n<td>Nhanh</td>\n<td>Hơi chậm</td>\n</tr>\n<tr>\n<td>Plugin ecosystem</td>\n<td>Tạm</td>\n<td>Ít</td>\n<td>Nhiều</td>\n<td>Rất nhiều</td>\n</tr>\n<tr>\n<td>Data layer</td>\n<td>File-based</td>\n<td>File-based</td>\n<td>Tự bơi</td>\n<td>GraphQL</td>\n</tr>\n<tr>\n<td>Học curve</td>\n<td>Dễ</td>\n<td>Dễ</td>\n<td>Trung bình</td>\n<td>Trung bình</td>\n</tr>\n</tbody>\n</table>\n<p>Lý do chính: <strong>mình là dân React</strong>. Viết blog bằng React component — có gì mà không thích? Thay vì ngồi học syntax template của Hugo hay Liquid của Jekyll, mình cứ JSX + TypeScript mà quất. Cái gì quen tay thì làm nhanh hơn, đúng không?</p>\n<p>Thêm nữa, Gatsby có <strong>GraphQL data layer</strong> rất hay. Bạn có thể lấy data từ markdown, CMS, API, file system… tất cả thông qua GraphQL, và Gatsby tự động tạo ra static HTML từ những data đó. Hiểu nôm na: bạn viết GraphQL query, Gatsby lo phần còn lại.</p>\n<p>Cuối cùng là <strong>plugin ecosystem</strong>. Cần SEO? <code class=\"language-text\">gatsby-plugin-react-helmet</code>. Cần syntax highlighting? <code class=\"language-text\">gatsby-remark-prismjs</code>. Cần sitemap? <code class=\"language-text\">gatsby-plugin-sitemap</code>. Cần responsive image? Cả tá plugin liên quan tới sharp. Gần như mọi thứ bạn cần cho 1 cái blog đều đã có người viết plugin, bạn chỉ việc <code class=\"language-text\">npm install</code> và thêm vào config là xong.</p>\n<h2>Kiến trúc Gatsby — hiểu trong 1 phút</h2>\n<p>Gatsby hoạt động theo flow thế này:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">Markdown files → Source plugin → GraphQL Data Layer → Templates → Static HTML</code></pre></div>\n<ol>\n<li><strong>Source plugins</strong> kéo data từ đâu đó vào (filesystem, CMS, API…)</li>\n<li>Data được chuẩn hóa thành <strong>GraphQL nodes</strong></li>\n<li><strong>Transformer plugins</strong> xử lý data (markdown → HTML, ảnh → responsive variants)</li>\n<li>Ở build time, Gatsby query GraphQL để lấy data và render thành <strong>trang HTML tĩnh</strong></li>\n<li>Sau build, bạn có 1 folder <code class=\"language-text\">public/</code> chứa toàn bộ website — chỉ cần upload lên bất kỳ static host nào</li>\n</ol>\n<p>Điểm mấu chốt: <strong>không có server, không có database, không có backend</strong>. Mỗi lần bạn viết bài mới, build lại là có ngay.</p>\n<h2>Setup cơ bản — bắt đầu từ đâu?</h2>\n<h3>1. Tạo project</h3>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> -g gatsby-cli\ngatsby new my-blog\n<span class=\"token builtin class-name\">cd</span> my-blog</code></pre></div>\n<p>Gatsby CLI sẽ tạo ra cấu trúc thư mục như sau:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">my-blog/\n├── src/\n│   ├── pages/          # Các trang tĩnh (index, 404)\n│   ├── templates/      # Template cho dynamic pages (blog post, category)\n│   ├── components/     # React components dùng chung\n│   └── blog/           # Chỗ để file markdown bài viết\n├── gatsby-config.js    # Config chính của site\n├── gatsby-node.js      # Logic tạo page động\n└── package.json</code></pre></div>\n<h3>2. Cấu hình path prefix</h3>\n<p>Vì blog mình deploy lên GitHub Pages dạng <code class=\"language-text\">username.github.io/blog/</code> nên cần path prefix:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// gatsby-config.js</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  pathPrefix<span class=\"token operator\">:</span> <span class=\"token string\">'/blog'</span><span class=\"token punctuation\">,</span>\n  siteMetadata<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    title<span class=\"token operator\">:</span> <span class=\"token string\">'Blog của tao'</span><span class=\"token punctuation\">,</span>\n    description<span class=\"token operator\">:</span> <span class=\"token string\">'Lôm côm, ba lăng nhăng'</span><span class=\"token punctuation\">,</span>\n    author<span class=\"token operator\">:</span> <span class=\"token string\">'@taolaai'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Khi build: <code class=\"language-text\">gatsby build --prefix-paths</code> — tất cả link sẽ tự động thêm <code class=\"language-text\">/blog</code> vào trước.</p>\n<h3>3. Source data từ Markdown</h3>\n<p>Đây là phần quan trọng nhất — làm sao để Gatsby biết bài viết của bạn ở đâu?</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// gatsby-config.js</span>\nplugins<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n  <span class=\"token punctuation\">{</span>\n    resolve<span class=\"token operator\">:</span> <span class=\"token string\">'gatsby-source-filesystem'</span><span class=\"token punctuation\">,</span>\n    options<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      path<span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>__dirname<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">/src/blog</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n      name<span class=\"token operator\">:</span> <span class=\"token string\">'blog'</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span>\n    resolve<span class=\"token operator\">:</span> <span class=\"token string\">'gatsby-transformer-remark'</span><span class=\"token punctuation\">,</span>\n    options<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      plugins<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token string\">'gatsby-remark-prismjs'</span><span class=\"token punctuation\">,</span>          <span class=\"token comment\">// syntax highlight code</span>\n        <span class=\"token string\">'gatsby-remark-images'</span><span class=\"token punctuation\">,</span>           <span class=\"token comment\">// responsive ảnh trong bài</span>\n        <span class=\"token string\">'gatsby-remark-external-links'</span><span class=\"token punctuation\">,</span>   <span class=\"token comment\">// target=\"_blank\" cho link ngoài</span>\n        <span class=\"token string\">'gatsby-remark-emojis'</span><span class=\"token punctuation\">,</span>           <span class=\"token comment\">// &lt;img class=\"emoji-icon\" alt=\"emoji-smile\" data-icon=\"emoji-smile\" style=\"display: inline; margin: 0; margin-top: 1px; position: relative; top: 5px; width: 25px\" src=\"data:image/png;base64, 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\" title=\"emoji-smile\" /> → emoji</span>\n        <span class=\"token string\">'gatsby-remark-copy-linked-files'</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// copy file đính kèm qua public/</span>\n      <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span></code></pre></div>\n<p><code class=\"language-text\">gatsby-source-filesystem</code> quét folder <code class=\"language-text\">src/blog/</code> và tạo ra các node cho mỗi file. Sau đó <code class=\"language-text\">gatsby-transformer-remark</code> parse markdown thành HTML, xử lý frontmatter, và các remark plugin con xử lý tiếp (highlight code, tối ưu ảnh, v.v.).</p>\n<p>Kết quả: mỗi file <code class=\"language-text\">src/blog/ten-bai/index.md</code> trở thành 1 GraphQL node kiểu <code class=\"language-text\">markdownRemark</code> với đầy đủ <code class=\"language-text\">html</code>, <code class=\"language-text\">excerpt</code>, <code class=\"language-text\">frontmatter</code> (title, date, category, tags…).</p>\n<h2>GraphQL Data Layer — trái tim của Gatsby</h2>\n<p>Một khi data đã vào GraphQL layer, bạn có thể query nó ở bất kỳ đâu. Ví dụ, query tất cả bài viết để hiển thị trên homepage:</p>\n<div class=\"gatsby-highlight\" data-language=\"graphql\"><pre class=\"language-graphql\"><code class=\"language-graphql\"><span class=\"token keyword\">query</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property-query\">allMarkdownRemark</span><span class=\"token punctuation\">(</span>\n    <span class=\"token attr-name\">filter</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token attr-name\">fields</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token attr-name\">draft</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token attr-name\">eq</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">false</span> <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">}</span>\n    <span class=\"token attr-name\">sort</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token attr-name\">fields</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token property\">frontmatter___date</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token attr-name\">order</span><span class=\"token punctuation\">:</span> <span class=\"token constant\">DESC</span> <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token object\">edges</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token object\">node</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token object\">fields</span> <span class=\"token punctuation\">{</span> <span class=\"token property\">slug</span> <span class=\"token punctuation\">}</span>\n        <span class=\"token object\">frontmatter</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token property\">title</span>\n          <span class=\"token property\">date</span>\n          <span class=\"token property\">category</span>\n          <span class=\"token property\">tags</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token property\">excerpt</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Để ý: <code class=\"language-text\">filter: { fields: { draft: { eq: false } } }</code> — đây là cách mình lọc bài nháp. <code class=\"language-text\">gatsby-plugin-draft</code> tự động thêm field <code class=\"language-text\">draft</code> dựa trên frontmatter, và mình chỉ việc filter nó trong GraphQL query.</p>\n<p><strong>Tại sao GraphQL mà không phải gì khác?</strong> Vì với GraphQL bạn chỉ query đúng data mình cần, không thừa không thiếu. Và Gatsby tối ưu hóa việc này ở build time — nó chạy tất cả GraphQL query, lấy data, và nhồi vào static HTML. Đến runtime thì không còn GraphQL nữa, chỉ có HTML + CSS + JS, load nhanh như chớp.</p>\n<h2>Tạo dynamic pages với gatsby-node.js</h2>\n<p>Có data rồi, giờ làm sao tạo ra từng trang riêng cho mỗi bài viết? Đây là việc của <code class=\"language-text\">gatsby-node.js</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">exports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">createPages</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">{</span> graphql<span class=\"token punctuation\">,</span> actions <span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> createPage <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> actions\n  <span class=\"token keyword\">const</span> blogPostTemplate <span class=\"token operator\">=</span> require<span class=\"token punctuation\">.</span><span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./src/templates/blog-post.tsx'</span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token function\">graphql</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n    {\n      allMarkdownRemark(\n        filter: { fields: { draft: { eq: false } } }\n        limit: 1000\n      ) {\n        edges {\n          node {\n            fields { slug }\n            frontmatter { title, category, tags }\n          }\n        }\n      }\n    }\n  </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">result</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> posts <span class=\"token operator\">=</span> result<span class=\"token punctuation\">.</span>data<span class=\"token punctuation\">.</span>allMarkdownRemark<span class=\"token punctuation\">.</span>edges\n\n    posts<span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">post<span class=\"token punctuation\">,</span> index</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">createPage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n        path<span class=\"token operator\">:</span> post<span class=\"token punctuation\">.</span>node<span class=\"token punctuation\">.</span>fields<span class=\"token punctuation\">.</span>slug<span class=\"token punctuation\">,</span>\n        component<span class=\"token operator\">:</span> blogPostTemplate<span class=\"token punctuation\">,</span>\n        context<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n          slug<span class=\"token operator\">:</span> post<span class=\"token punctuation\">.</span>node<span class=\"token punctuation\">.</span>fields<span class=\"token punctuation\">.</span>slug<span class=\"token punctuation\">,</span>\n          previous<span class=\"token operator\">:</span> index <span class=\"token operator\">===</span> posts<span class=\"token punctuation\">.</span>length <span class=\"token operator\">-</span> <span class=\"token number\">1</span> <span class=\"token operator\">?</span> <span class=\"token keyword\">null</span> <span class=\"token operator\">:</span> posts<span class=\"token punctuation\">[</span>index <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>node<span class=\"token punctuation\">,</span>\n          next<span class=\"token operator\">:</span> index <span class=\"token operator\">===</span> <span class=\"token number\">0</span> <span class=\"token operator\">?</span> <span class=\"token keyword\">null</span> <span class=\"token operator\">:</span> posts<span class=\"token punctuation\">[</span>index <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>node<span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Flow hoạt động:</p>\n<ol>\n<li>Query tất cả markdown đã publish</li>\n<li>Với mỗi bài, gọi <code class=\"language-text\">createPage()</code> — truyền slug làm path, và truyền <code class=\"language-text\">previous</code>/<code class=\"language-text\">next</code> để tạo nút điều hướng bài trước/sau</li>\n<li>Gatsby sẽ render template <code class=\"language-text\">blog-post.tsx</code> với data từ <code class=\"language-text\">context</code> cho mỗi page</li>\n</ol>\n<p>Template blog-post nhận <code class=\"language-text\">pageContext.slug</code> rồi tự query data của bài đó:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">export</span> <span class=\"token keyword\">const</span> pageQuery <span class=\"token operator\">=</span> graphql<span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n  query BlogPostBySlug($slug: String!) {\n    markdownRemark(fields: { slug: { eq: $slug } }) {\n      html\n      excerpt\n      frontmatter { title, date, category, tags }\n    }\n  }\n</span><span class=\"token template-punctuation string\">`</span></span></code></pre></div>\n<p>Và phần render thì đơn giản:</p>\n<div class=\"gatsby-highlight\" data-language=\"jsx\"><pre class=\"language-jsx\"><code class=\"language-jsx\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">dangerouslySetInnerHTML</span><span class=\"token script language-javascript\"><span class=\"token script-punctuation punctuation\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> __html<span class=\"token operator\">:</span> post<span class=\"token punctuation\">.</span>html <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span></span> <span class=\"token punctuation\">/></span></span></code></pre></div>\n<p>Nghe “dangerously” nhưng thực ra an toàn vì HTML này được generate từ markdown của chính bạn, không phải từ user input.</p>\n<p>Tương tự cho category và tag pages — mình dùng <code class=\"language-text\">createPage()</code> để tạo <code class=\"language-text\">/categories/ten-category/</code> và <code class=\"language-text\">/tags/ten-tag/</code> từ danh sách unique category/tag lấy được từ frontmatter.</p>\n<h2>Plugin ecosystem — những viên gạch làm nên blog</h2>\n<p>Đây là danh sách plugin mình đang dùng, kèm lý do tại sao nên hoặc không nên cài:</p>\n<table>\n<thead>\n<tr>\n<th>Plugin</th>\n<th>Mục đích</th>\n<th>Nên dùng?</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-typescript</code></td>\n<td>Viết component bằng TypeScript</td>\n<td>Nếu bạn xài TS</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-react-helmet</code></td>\n<td>Quản lý thẻ <code class=\"language-text\">&lt;head></code>, SEO, OG tags</td>\n<td>Luôn luôn</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-image</code> + <code class=\"language-text\">sharp</code></td>\n<td>Ảnh responsive, lazy load, blur-up placeholder</td>\n<td>Luôn luôn</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-manifest</code></td>\n<td>PWA manifest, icon cho mobile</td>\n<td>Nên</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-sitemap</code></td>\n<td>Tự sinh sitemap.xml cho SEO</td>\n<td>Nên</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-robots-txt</code></td>\n<td>Tự sinh robots.txt</td>\n<td>Nên</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-draft</code></td>\n<td>Lọc bài nháp, không publish nhầm</td>\n<td>Có viết draft thì nên</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-plugin-offline</code></td>\n<td>Service worker, offline mode</td>\n<td><strong>Mình bỏ rồi</strong> vì service worker cache quá aggressive, code cũ không tự update</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">gatsby-remark-prismjs</code></td>\n<td>Highlight syntax code trong markdown</td>\n<td>Có viết code thì phải có</td>\n</tr>\n<tr>\n<td><code class=\"language-text\">@weknow/gatsby-remark-twitter</code></td>\n<td>Nhúng tweet vào bài viết</td>\n<td>Nếu hay dẫn tweet</td>\n</tr>\n</tbody>\n</table>\n<p>Tip: đừng cài plugin chỉ vì nó có vẻ ngầu. Mỗi plugin là 1 dependency, thêm vào là thêm maintenance burden. Mình từng cài <code class=\"language-text\">gatsby-plugin-offline</code> vì nghĩ “ồ offline mode, ngầu vãi” — xong rồi phải cài thêm <code class=\"language-text\">gatsby-plugin-remove-serviceworker</code> để gỡ nó ra vì nó cache lỗi và người đọc không thấy bài mới.</p>\n<h2>Deploy lên GitHub Pages — đỉnh cao của sự lười biếng</h2>\n<p>Đây là phần khiến mình sướng nhất: <strong>deploy hoàn toàn miễn phí, tự động 100%, không cần server</strong>.</p>\n<p>Ý tưởng:</p>\n<ol>\n<li>Source code để ở branch <code class=\"language-text\">develop</code></li>\n<li>Mỗi lần push lên <code class=\"language-text\">develop</code>, CI tự chạy <code class=\"language-text\">gatsby build</code></li>\n<li>Output (thư mục <code class=\"language-text\">public/</code>) được deploy lên branch <code class=\"language-text\">master</code></li>\n<li>GitHub Pages phục vụ từ branch <code class=\"language-text\">master</code> với path prefix <code class=\"language-text\">/blog</code></li>\n</ol>\n<p>Trước đây mình xài Travis CI, nhưng giờ chuyển qua GitHub Actions cho gọn — đỡ phải maintain 2 nơi. Workflow đơn giản như sau:</p>\n<div class=\"gatsby-highlight\" data-language=\"yaml\"><pre class=\"language-yaml\"><code class=\"language-yaml\"><span class=\"token key atrule\">name</span><span class=\"token punctuation\">:</span> Deploy to GitHub Pages\n\n<span class=\"token key atrule\">on</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">push</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">branches</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span>develop<span class=\"token punctuation\">]</span>\n\n<span class=\"token key atrule\">jobs</span><span class=\"token punctuation\">:</span>\n  <span class=\"token key atrule\">build-and-deploy</span><span class=\"token punctuation\">:</span>\n    <span class=\"token key atrule\">runs-on</span><span class=\"token punctuation\">:</span> ubuntu<span class=\"token punctuation\">-</span>latest\n    <span class=\"token key atrule\">steps</span><span class=\"token punctuation\">:</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/checkout@v3\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> actions/setup<span class=\"token punctuation\">-</span>node@v3\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">node-version</span><span class=\"token punctuation\">:</span> <span class=\"token string\">'16'</span>\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn install <span class=\"token punctuation\">-</span><span class=\"token punctuation\">-</span>frozen<span class=\"token punctuation\">-</span>lockfile\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">run</span><span class=\"token punctuation\">:</span> yarn build\n      <span class=\"token punctuation\">-</span> <span class=\"token key atrule\">uses</span><span class=\"token punctuation\">:</span> peaceiris/actions<span class=\"token punctuation\">-</span>gh<span class=\"token punctuation\">-</span>pages@v3\n        <span class=\"token key atrule\">with</span><span class=\"token punctuation\">:</span>\n          <span class=\"token key atrule\">github_token</span><span class=\"token punctuation\">:</span> $<span class=\"token punctuation\">{</span><span class=\"token punctuation\">{</span> secrets.GITHUB_TOKEN <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span>\n          <span class=\"token key atrule\">publish_dir</span><span class=\"token punctuation\">:</span> ./public\n          <span class=\"token key atrule\">publish_branch</span><span class=\"token punctuation\">:</span> master</code></pre></div>\n<p>Thế là xong. Mỗi lần viết bài mới, chỉ cần:</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">git</span> <span class=\"token function\">add</span> src/blog/bai-moi/index.md\n<span class=\"token function\">git</span> commit -m <span class=\"token string\">\"new post: bài mới\"</span>\n<span class=\"token function\">git</span> push origin develop</code></pre></div>\n<p>Vài phút sau bài viết đã xuất hiện trên <code class=\"language-text\">chungtran4078.github.io/blog/</code>. Không cần SSH, không cần FTP, không cần mở mắt dậy sớm. Chỉ cần <code class=\"language-text\">git push</code> và cầu nguyện cho CI đừng fail.</p>\n<p>Tổng chi phí vận hành blog: <strong>0 đồng</strong>. Không tiền server, không tiền domain (xài <code class=\"language-text\">username.github.io</code>), không tiền CDN (GitHub Pages chạy trên Fastly), không tiền CI (GitHub Actions free cho public repo). Giá mà mọi thứ trong đời đều free như này nhỉ.</p>\n<h2>Vài thứ bạn nên biết trước khi lao vào</h2>\n<p><strong>1. Build time tăng dần theo số lượng bài viết.</strong> Hiện tại blog mình 14 bài, build mất ~2 phút. Nếu bạn có 500 bài, có thể mất 10-15 phút. Gatsby có <code class=\"language-text\">gatsby develop</code> để dev cực nhanh (chỉ build page đang xem), nhưng production build thì phải chịu. Nên nếu blog nhiều bài, cân nhắc Hugo hoặc 11ty.</p>\n<p><strong>2. Gatsby 3 → 4 → 5</strong>, version nhảy như châu chấu. Blog này đang ở Gatsby 3, lên 4 hay 5 cũng được nhưng mà lười. Breaking changes giữa các major version là có thật, nên nếu đang chạy ổn thì đừng vội upgrade chỉ vì FOMO.</p>\n<p><strong>3. Hosting trên <code class=\"language-text\">username.github.io</code> chỉ được 1 site.</strong> Nếu muốn nhiều project site, bạn phải dùng custom domain hoặc repo riêng kiểu <code class=\"language-text\">username.github.io/project-name/</code>. Cái này GitHub Pages hỗ trợ tốt.</p>\n<p><strong>4. Không có backend</strong>, nghĩa là không có comment, không có form, không có search server-side. Muốn có comment thì nhúng Disqus hay Giscus. Muốn search thì dùng <code class=\"language-text\">gatsby-plugin-local-search</code> hoặc Algolia. Nhưng thú thật, mình thấy để blog không có comment cũng hay — đỡ phải trả lời mấy ông vào hỏi “sao code t copy từ bài của bạn không chạy?“.</p>\n<h2>Tổng kết</h2>\n<p>Gatsby + GitHub Pages là combo hoàn hảo cho dân IT xài React, muốn viết blog nhanh, đẹp, free, và quan trọng nhất: <strong>toàn quyền kiểm soát</strong>. Không ai chèn quảng cáo vào bài viết của bạn, không ai bắt khách đọc phải đăng ký tài khoản, không ai giới hạn bandwidth.</p>\n<p>Nhược điểm thì cũng có: build hơi chậm, config hơi nhiều, version Gatsby update nhanh vl. Nhưng đổi lại, bạn có 1 cái blog mà bạn hiểu từng dòng code, toàn bộ source nằm trong tầm tay, và chi phí là 0 đồng trọn đời. Quá hời.</p>\n<p>Nếu bạn đang đọc bài này và cũng đang chán Medium/WordPress, thì: <code class=\"language-text\">npm install -g gatsby-cli</code> và bắt đầu thôi. Nhớ quay lại đây khoe blog mới nhé, để mình còn vô comment dạo.</p>","frontmatter":{"title":"Gatsby + GitHub Pages = Blog miễn phí trọn đời, nghe như lừa đảo nhưng thật 100%","date":"2026-07-20","category":"Frontend","tags":["gatsby","react","github","blog"],"image":null}}},"pageContext":{"slug":"/gatsby-github-pages-blog-mien-phi/","previous":{"fields":{"slug":"/phan-phoi-xu-ly-trong-nodejs-cluster/"},"frontmatter":{"title":"Phân phối xử lý trong Nodejs P3: Cluster","category":"Nodejs","tags":["nodejs","performance","optimization"]}},"next":null}},
    "staticQueryHashes": ["3649515864","3894061898"]}