gatsby建立部落格詳細教程及避坑攻略
首先先放上我的主頁
這篇文章算是對這段時間折騰的一個東西的技術總結,在之前文章部落格遷移中,我已經總結了項目的背景,技術選型等形而上的內容,這篇文章主要是技術總結,並且幫助大家能從我的視角了解gatsby,並且快速的上手實踐。
Gatsby是什麼,為什麼要用Gatsby
Gatsby實際上是是一個react的靜態化框架。 React本身實際上能完成所有gatsby的功能。但是有一個問題就是,react是一個單頁應用(SPA - single page application),本身實際上是在前端動態的生成頁面。這樣就會有兩個問題,一個是首屏加載時間很長。第二個因為頁面是動態生成,所以搜索引擎不知道每個頁面的內容,對SEO很不友好。對於這些問題,react的服務端渲染框架(SSR - server side render)應運而生。而SSR本身也有很多不同的實現方案,有些是直接搭一個渲染服務器多一個中間層,而有一些就比較極端,比如gatsby,直接在編譯的時候就把整個網站靜態化。這樣上面的問題就迎刃而解了。
gatsby如何工作
react吸引人的特點之一就是她的動態特性,如果在編譯的時候就把整個網站靜態化,那麼這些動態的特性該如何保證呢? Gatsby給出的方案就是程序化生成頁面。具體的說,gatsby的頁面生成有兩種方式,一個是直接把react組件寫在/src/pages
目錄下,比如/src/pages/tags.js
對應的頁面就是/tags
。第二種就是通過gatsby的api動態的構建頁面了,官方的說明文檔在這裡.簡而言之就是1.創建頁面模板2.通過graphQL獲取用於生成頁面的數據3.在gatsby-node.js中通過createPage
和onCreateNode
兩個API生成頁面
實戰
實戰部分手把手教你創建一個和我一樣的部落格
創建項目
首先你需要安裝gatsby及其依賴
然後通過部落格模板創建項目
$ gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog
然後
$ cd my-blog-stater && npm start
就可以打開瀏覽器輸入localhost:8000
看到主頁了

增加tags相關頁面
如果你想像我的主頁一樣擁有tags頁面的話。可以通過以下操作
增加tag標籤
要增加tags界面,首先得有tags。要做的很簡單,就是在你的markdown文章中增加tags標籤如圖


文章在/content/blog/{title}
這個路徑中
生成網頁
tags相關的頁面有兩個,一個是/tags
,另一個是/tag/{tag}
,前一個用於展示所有的tag,其中點擊任意一個tag就是跳轉到對應tag的文章列表,也就是後面的頁面。
因為/tags
依賴/tag/{tag}
,所以我們首先我們生成/tag/{tag}
,根據前面介紹gatsby如何工作
首先我們需要創建一個頁面模板/src/templates/tag-posts.js
import React from "react" import { graphql, Link } from "gatsby" const TagPost = ({data, pageContext}) => { const posts = data.allMarkdownRemark.edges return ( <div> <h1>Posts for tag: {pageContext.targetTag}</h1> <p>{posts.length + " posts"}</p> <hr /> <ul> {posts.map(({ node }) => { const title = node.frontmatter.title || node.fields.slug return ( <li><Link to={node.fields.slug}>{title}</Link></li> ) })} </ul> </div> ) } export default TagPost export const pageQuery = graphql` query($targetTag: String!) { site { siteMetadata { title } } allMarkdownRemark(filter: {frontmatter: {tags: {eq: $targetTag}}}, sort: {fields: frontmatter___date, order: DESC}) { edges { node { excerpt(truncate: true) fields { slug } frontmatter { date(formatString: "MMMM DD, YYYY") title tags } } } } } `
然後按照第二部,在gatsby-node.js
中生成頁面,添加如下代碼
exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions /*此处省略生成blog-post的代码*/ // start generate tag posts const tagPost = path.resolve(`./src/templates/tag-posts.js`) const tagPostResult = await graphql( ` { allMarkdownRemark { nodes { frontmatter { tags } } } } ` ) if (tagPostResult.errors) { throw tagPostResult.errors } const nodes = tagPostResult.data.allMarkdownRemark.nodes //extract distict tags of all posts var tagSet = new Set() nodes.forEach(node => node.frontmatter.tags.forEach(tag => tagSet.add(tag))) // gen page for each tag tagSet.forEach( tag => createPage({ path: "tag/" + tag, component: tagPost, context: { targetTag : tag }, })) }
之後你重新npm start
,然後輸入網址http://localhost:8000/tag/tag2
,你就能看到下面的頁面

現在我們要生成/tags
的頁面,這個不是一個動態路徑,所以我們在/src/pages
添加一個js文件就可以了/src/pages/tags.js
,裡面的代碼如下
import React from "react" import { Link, graphql } from "gatsby" const Tags = ({ data }) => { const posts = data.allMarkdownRemark.edges var tagMap = new Map() for (const post of posts) { for (const tag of post.node.frontmatter.tags) { if (tagMap.has(tag)) { tagMap.set(tag, tagMap.get(tag) + 1) } else { tagMap.set(tag, 1) } } } var tagPair = Array.from(tagMap) tagPair.sort((left, right) => right[1] - left[1]) console.log(tagPair) return ( <div> <h1>All Tags</h1> <p>Click the tag to read related articles</p> <hr /> <ul> {tagPair.map(([tag, count]) => { return ( <li><Link to={"tag/" + tag} className="tag">{tag + " | " + count + " posts"}</Link></li> ) })} </ul> </div> ) } export default Tags export const pageQuery = graphql` query { allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) { edges { node { frontmatter { tags } } } } } `
然後再瀏覽器輸入http://localhost:8000/tags
,就可以看到下面的頁面

這兩類頁面就覆蓋了兩種頁面的生成方式,其他的頁面就靠大夥舉一反三了
優化
增加css效果
剛才生成的頁面中,都是光禿禿的文字,為了頁面更美觀,需要加上css效果使其更美觀,部落格本身的css框架我並不是很喜歡,所以我換了一個我比較喜歡另一個輕便的css框架BULMA
這個框架的特點就是簡單輕巧,只有css,沒有js,不容易和其他的組件產生衝突。而且非常的易用。
官方有介紹如何接入的文檔
具體如何使用,或者想直接抄作業可以直接看我的源碼
組件化
組件就是很多頁面需要公用的模塊就會抽象成組件,這樣會使代碼變得優雅易懂。
比如每個頁面都要頁眉頁腳,這些就可以抽像出Layout組件。每個頁面又要不同的元訊息做SEO,這就抽像出了SEO組件。比如我覺得展示文章需要用統一的卡片樣式,我自己就抽像出了PostCard組件。還有每個頁面可能都需要的Comment組件。
因為gatsby本身就是從react擴展出來的框架,自然也可以使用組件來優雅的開發界面。組件所在的路徑是/src/components/
。裡面已經有了Layout
和SEO
等組件。相關的語法需要查閱一下react的文檔。依葫蘆畫瓢寫還是比較簡單的。
具體的怎麼寫可以直接看我的源碼
第三方評論
之前我的hexo部落格用的是國內的多說,但是多說已經不運營了。但是我又不想自己額外維護一個評論數據庫。所以我調研了一圈,對比了一番發現幾個可能可用的
- Disqus - 這個是世界上最大也是最成熟的,但是在台灣被牆了。 pass
- gitcomment - 這個是通過github issue來實現的,強制用github登錄。對於非碼農的評論者不友好,而且github在台灣api的效率也存疑。 pass
- levere(來必力) - 這個是韓國的一個第三方模塊,對台灣的本地化支持的很好,支持簡單回復和包括微信在內的各種第三方賬號登錄。看他的客戶也不像會倒的樣子,所以就用他了。
levere有個問題就是在移動端打開的時候無法微信登錄,這個我諮詢了他們的客服,他們回复這個就是這樣,因為微信得掃碼,所以無法移動端登錄。無解。而且在集成的時候還碰到了一些坑,見下文。
託管
整個網頁託管到了github page上,具體怎麼託管,請翻閱文檔
自動化
因為gatsby出色的架構,整個網站現在只需要增加markdown文章,其他的任何內容都不需要更改就會自動的構建並發布新網站。但是我簡直懶到了家,為了實現hexo一樣,只要hexo new就可以寫新文章的便捷,我寫了一個腳本gen-new-post.js ,然後再/package.json
的script
中增加一條命令
"new": "node gen-new-post.js $*",
這樣,我們就可以用npm run new {newtitle}
自動生成markdown文件啦。
踩坑
graphQL相關
你的每個md文件都要加上tags:[]
,不然graphQL讀出來的話這個屬性就是null而不會是空的array,那樣的話tags.foreach
和tags.map()
就會報錯。
Link 和a,兩種不同的跳轉
官方介紹說Link
是用來跳轉到應用內的頁面的,而a
則是用來跳轉到外鏈的。但是這樣其實是有一個問題,一開始我的評論模塊時通過helmet
模塊把第三方評論的腳本寫到每個post的頁面的header裡面。第一次點擊文章沒有問題,但是從一篇文章通過點擊下一篇文章的鏈接跳轉的時候,評論模塊就消失了。在網上查了一下,裡面有一個解決方案是吧Link改成a。確實有效。但是我進一步探究了一下為什麼。
查了很多資料後我知道了原因,gatsby的Link實際上和react的Route類似,會做優化做預加載,加快跳轉速度。通過Link跳轉的話,其實用的是react特有的diff渲染,這種渲染只會渲染兩個頁面不同的部分。因為兩篇post的header部分幾乎完全相同,所以在渲染的時候評論模塊的script是不會重新生成的,而評論模塊時script腳本動態生成的,所以就消失了。
使用a的話,會強制整個頁面進行更新,這個自然是可行的,但是就沒有了預加載的優勢。頁面的跳轉就不會快如閃電,不夠優雅。
其實這個可以通過一個優雅的方式解決。就是react的hook
。在Comment組件中增加一個hook - useEffect。
const Comment = () => { useEffect(() => { var j, e = document.getElementsByTagName("script")[0]; if (typeof LivereTower === 'function') { return; } j = document.createElement("script"); j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; j.async = true; e.parentNode.insertBefore(j, e); }); return ( <div id="lv-container" data-id="city" data-uid="xxxxx"> </div> ) }
useEffect
的作用是每次在組件加載和更新的時候,會調用一次這個方法。而comment組件又是嵌在tag-post中的,所以每次切換文章,tag-post肯定需要重新加載,所以comment也需要重新加載,而useEffect就會動態的插入一個script,這個新插入的script就會動態生成一個第三方評論框,問題解決。