使用Gridsome搭建博客过程

Default starter for Gridsome

This is the project you get when you run gridsome create new-project.

1. Install Gridsome CLI tool if you don't have

npm install --global @gridsome/cli

2. Create a Gridsome project

  1. gridsome create my-gridsome-site to install default starter
  2. cd my-gridsome-site to open the folder
  3. gridsome develop to start a local dev server at http://localhost:8080
  4. Happy coding 🎉🙌

项目搭建过程

  • 项目初始化

    • Fork此项目到自己的仓库
    • 将 Fork 到的项目添加到当前工作区,便于复制
    • 安装 bootstrap 和@fortawesome/fontawesome-free npm 方式
    • 在 main.js 中加载全局样式
    • 加载谷歌字体 assets/css/index.css 中 @import 方式引入
    • 引入样式
    • 将 index.html 中的内容复制到 pages/index.vue 中
    • 启动项目,访问正常,需要复制图片到 static/img/目录下,修改图片路径为/
    • 将<nav>和<footer>标签放入 layouts/Default.vue 中,添加默认插槽<slot />
    • 添加页面 Post.vue、About.vue、Contact.vue,初始化页面完成
  • 使用本地 md 文件管理文章内容

    • 在 GridSome 官网找到source-filesystem插件,转化文件内容为可以在组件中使用 GraphQL 获取的内容。
    • npm install @gridsome/source-filesystem安装
    • 在 gridsome.config.js 的 plugins 中进行配置
      plugins: [
        {
          use: '@gridsome/source-filesystem', // 插件
          options: {
            typeName: 'BlogPost', // 类型,对应GraphQL中的查询
            path: './content/blog/**/*.md', // 文件路径
          },
        },
      ],
      
    • 创建 content/blog/article1.md 和 arttcle2.md 文件
    • 重启项目,报错
      Error: No transformer for 'text/markdown' is installed.
      
    • 原因是需要 Gridsome 的 Markdown 变换器@gridsome/transformer-remark,安装并重启
    • 打开 GraphQL 操作页面,查询数据得到结果,然后既可以渲染到页面上
      query {
        allBlogPost {
          edges {
            node {
              id
              path
              title
              content
            }
          }
        }
      }
      
  • 使用 strapi 接口数据

  • 快速开始安装并启动 strapi 应用,安装成功打开http://localhost:1337/,创建 Content Type,比如 post,添加 id、title 和 content 字段

    • 分配权限

      需要给 publish 角色以下权限,就可在 postman 中使用

      image-20210401081645397.png
  • 修改 Authenticated 权限,全选,添加用户,设置角色为 Authenticated

  • 在 postman 测试登陆

    localhost:1337/auth/local
    {
      "identifier": "756638369@qq.com",
      "password": "123456"
    }
    {
      "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjE3MjM2NTM1LCJleHAiOjE2MTk4Mjg1MzV9.8Ha5sZxLwSKERdQH3OOvWU9qNsGw2fabpL4uxuChJvQ",
      "user": {
          "id": 1,
          "username": "wangxiang",
          "email": "756638369@qq.com",
          "provider": "local",
          "confirmed": false,
          "blocked": false,
          "role": {
              "id": 1,
              "name": "Authenticated",
              "description": "Default role given to authenticated user.",
              "type": "authenticated"
          },
          "created_at": "2021-04-01T00:20:37.396Z",
          "updated_at": "2021-04-01T00:20:37.405Z"
      }
    }
    
    • 得到 jwt 就可以请求了

    • 可以使用 GraphQL 进行安装,安装 npm run strapi install graphql

    • 安装完成,打开 http://localhost:1337/graphql即可访问

    • 如何把 strapi 数据通过预取的方式集成到 gridsome 中,想要实现渲染前就拿到 strapi 中的数据,借助 gridsome 插件 strapi

    • 使用

      export default {
        plugins: [
          {
            use: '@gridsome/source-strapi',
            options: {
              apiURL: 'http://localhost:1337',
              queryLimit: 1000, // Defaults to 100
              contentTypes: ['article', 'user'],
              singleTypes: ['impressum'],
              // Possibility to login with a Strapi user,
              // when content types are not publicly available (optional).
              loginData: {
                identifier: '',
                password: '',
              },
            },
          },
        ],
      }
      
    • 重启项目,启动完成后就可以在 grapjQL 中访问 strapi 中的数据了

    • strapi 新增或删除数据后,gridsome 需要重启项目,因为在启动过程中拉取数据

      image-20210401091814427.png
  • 删除 post,重新创建,添加字段 title、content、cover、is_publish、created_name 关联 user

  • 创建 tags

  • 重启 gridsome,发现报错,因为没有权限,重置 post 和 tag 的权限

  • 修改页面,添加查询

    <div
      v-for="item in $page.posts.edges"
      class="post-preview"
      :key="item.node.id"
    >
      <a href="post.html">
        <h2 class="post-title">
          {{ item.node.title }}
        </h2>
        <h3 class="post-subtitle">
          {{ item.node.title }}
        </h3>
      </a>
      <p class="post-meta">
        Posted by
        <a href="#"
          >{{ item.node.created_name.firstname + item.node.created_name.lastname
          }}</a
        >
        {{ item.node.created_at }}
      </p>
      <span v-for="tag in item.node.tags" :key="tag.id">
        <a href="">{{ tag.title }}</a>
        &nbsp;&nbsp;
      </span>
      <hr />
    </div>
    
    <page-query>
      query { posts: allStrapiPost { edges { node { id title created_name { id
      firstname lastname } created_at updated_at tags { id title } } } } }
    </page-query>
    
  • 加载分页组件

    引入并注册 Pager 组件,页面中添加,查询时添加 pageInfo

    ...
    <!-- Pager -->
    <Pager :info="$page.posts.pageInfo"/>
    ...
    
    query ($page: Int){
        posts: allStrapiPost (perPage: 2, page: $page) @paginate{
        pageInfo {
          totalPages
          currentPage
        }
            edges {
          node {
            id
            title
            created_name {
              id
              firstname
              lastname
            }
            created_at
            updated_at
            tags {
              id
              title
            }
          }
        }
      }
    }
    </page-query>
    import { Pager } from 'gridsome'
    export default {
      name: 'Home',
      components: {
        Pager
      },
    }
    
  • 加载文章详情

    修改 gridsome.config.js,添加 templates

     // 详情的模板页面 根据对应内容类型创建模板
    // 模板名称StrapiPost一定要写集合的名字 此时集合由'@gridsome/source-strapi'生成
    templates: {
        StrapiPost: [
            {
                path: '/post/:id', // 详情对应路由
                component: './src/templates/Post.vue',
            },
        ]
    },
    

    创建 templates/Post.vue 文章详情模板

    查询数据,替换模板

    <template>
      <Layout>
        <!-- Page Header -->
        <header
          class="masthead"
          :style="{
            backgroundImage: `url(http://localhost:1337${
              $page.post.cover.url
            })`,
          }"
        >
          <div class="overlay"></div>
          <div class="container">
            <div class="row">
              <div class="col-lg-8 col-md-10 mx-auto">
                <div class="post-heading">
                  <h1>{{ $page.post.title }}</h1>
                  <h2 class="subheading">
                    {{ $page.post.title }}
                  </h2>
                  <span class="meta"
                    >Posted by
                    <a href="#">{{
                      $page.post.created_name.firstname +
                        $page.post.created_name.lastname
                    }}</a>
                    on {{ $page.post.created_at }}</span
                  >
                </div>
              </div>
            </div>
          </div>
        </header>
    
        <!-- Post Content -->
        <article>
          <div class="container">
            <div class="row">
              <div
                class="col-lg-8 col-md-10 mx-auto"
                v-html="$page.post.content"
              ></div>
            </div>
          </div>
        </article>
      </Layout>
    </template>
    
    <page-query>
    query ($id: ID!){
        post: strapiPost  (id: $id) {
            id
        title
        content
        cover {
          url
        }
        created_name {
          id
          firstname
          lastname
        }
        created_at
        tags {
          id
          title
        }
      }
    }
    </page-query>
    <script>
    export default {
      name: 'Post',
      metaInfo: {
        title: 'Post',
      },
    }
    </script>
    <style></style>
    

    修改/pages/index.vue,a 标签为 g-link 标签

    ...
    <g-link :to="`/post/${item.node.id}`">
        <h2 class="post-title">
            {{ item.node.title }}
        </h2>
        <h3 class="post-subtitle">
            {{ item.node.title }}
        </h3>
    </g-link>
    ...
    
  • 处理 markdown 格式文档

    使用markdown-it处理 markdown 格式数据

    <template>
      ...
      <div
        class="col-lg-8 col-md-10 mx-auto"
        v-html="mdToHtml($page.post.content)"
      ></div>
      ...
    </template>
    <script>
    import MarkdownIt from 'markdown-it'
    const md = new MarkdownIt()
    
    export default {
      name: 'Post',
      metaInfo: {
        title: 'Post',
      },
      methods: {
        mdToHtml(markdown) {
          return md.render(markdown)
        },
      },
    }
    </script>
    
  • 文章标签

    修改 gridsome.config.js,contentTypes 添加 tag

        {
          use: '@gridsome/source-strapi',
          options: {
            apiURL: 'http://localhost:1337',
            queryLimit: 1000, // Defaults to 100
            contentTypes: ['post', 'tag'], // StrapiPost
            // typeName: 'Strapi',
            // singleTypes: ['impressum'],
            // Possibility to login with a Strapi user,
            // when content types are not publicly available (optional).
            // loginData: {
            //   identifier: '',
            //   password: '',
            // },
          },
        },
    

    添加 tag 模板

    StrapiTag: [
      {
        path: '/tag/:id', // 详情对应路由
        component: './src/templates/Tag.vue',
      },
    ]
    

    创建/templates/Tag.vue 模板

    <template>
      <Layout>
        <!-- Page Header -->
        <header
          class="masthead"
          style="background-image: url('/img/home-bg.jpg')"
        >
          <div class="overlay"></div>
          <div class="container">
            <div class="row">
              <div class="col-lg-8 col-md-10 mx-auto">
                <div class="site-heading">
                  <h1># {{ $page.tag.title }}</h1>
                </div>
              </div>
            </div>
          </div>
        </header>
    
        <!-- Main Content -->
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <div
                v-for="item in $page.tag.posts"
                class="post-preview"
                :key="item.id"
              >
                <g-link :to="`/post/${item.id}`">
                  <h2 class="post-title">
                    {{ item.title }}
                  </h2>
                  <h3 class="post-subtitle">
                    {{ item.title }}
                  </h3>
                </g-link>
                <hr />
              </div>
              <!-- Pager -->
              <Pager :info="$page.posts.pageInfo" />
            </div>
          </div>
        </div>
      </Layout>
    </template>
    <page-query>
    query ($id: ID!){
        strapiTag  (id: $id) {
        id
        title
        posts {
          id
          title
        }
      }
    }
    </page-query>
    
    <script>
    export default {
      name: 'Tag',
    }
    </script>
    <style></style>
    

    修改/pages/index.vue,a 标签为 g-link 标签

    ...
    <span v-for="tag in item.node.tags" :key="tag.id">
        <g-link href="" :to="`/tag/${tag.id}`">{{ tag.title }}</g-link>
        &nbsp;&nbsp;
    </span>
    ...
    
  • 基本设置

    设置博客首页标题、副标题和封面

    处理网站标题和副标题、包括网站首页封面,都可以统一管理起来,设计统一数据结构,在页面展示即可

    Content Type 相当于集合,而此时只需要创建单个的数据节点,选择 Single Type

    在 strapi 中新增一个 Single Type(单一类型),名称为 General,并添加三个字段,title、subtitle 和 cover

    保存成功后,在 general 中添加相应字段并保存,对其配置 find 权限

    接着集成到网站当中去使用,配置 gridsome.config.js 的 plugins 选项

    ...
    {
        use: '@gridsome/source-strapi',
        options: {
            apiURL: 'http://localhost:1337',
            queryLimit: 1000, // Defaults to 100
            contentTypes: ['post', 'tag'], // StrapiPost配置集合
            // typeName: 'Strapi',
            singleTypes: ['general'], // 配置单节点
            // Possibility to login with a Strapi user,
            // when content types are not publicly available (optional).
            // loginData: {
            //   identifier: '',
            //   password: '',
            // },
    },
    },
    ...
    

    在/pages/index.vue`中,读取 GraphQL 数据层的数据,并在视图中渲染

    <template>
      <Layout>
        <header
          class="masthead"
          :style="{
            backgroundImage: `url(http://localhost:1337${general.cover.url})`,
          }"
        >
          <div class="overlay"></div>
          <div class="container">
            <div class="row">
              <div class="col-lg-8 col-md-10 mx-auto">
                <div class="site-heading">
                  <h1>{{ general.title }}</h1>
                  <span class="subheading">{{ general.subtitle }}</span>
                </div>
              </div>
            </div>
          </div>
        </header>
        ...
      </Layout>
    </template>
    
    <page-query>
    query ($page: Int){
       ...
      allStrapiGeneral {
        edges {
          node {
            id
            title
            subtitle
            cover {
              url
            }
          }
    }
      }
    }
    </page-query>
    
    <script>
    ...
      // 使用计算属性
      computed: {
        general() {
          return this.$page.allStrapiGeneral.edges[0].node
        },
      },
    ...
    </script>
    <style></style>
    
  • 联系我页面实现

    使用纯客户端实现并将数据保存

    创建contact集合,根据页面需要添加name、email、phone和message字段,并赋予contact集合create权限

    使用postman进行测试,localhost:1337/contacts添加数据并测试

    修改/pages/contact.vue页面

    <template>
      <!-- 使用v-model绑定表单数据、并添加按钮点击事件 -->
    </template>
    
    <script>
    import axios from 'axios'
    export default {
      name: 'Contact',
      metaInfo: {
        title: 'Contact',
      },
      data() {
        return {
          form: {
            name: '',
            email: '',
            phone: '',
            message: '',
          },
        }
      },
      methods: {
        // 此处应该加入表单校验
        async onSubmit() {
          try {
            const { data } = await axios({
              method: 'POST',
              url: 'http://localhost:1337/contacts',
              data: this.form,
            })
            this.form = {
              name: '',
              email: '',
              phone: '',
              message: '',
            }
            window.alert('发送成功')
          } catch (err) {
            window.alert('发送失败')
            throw new Error(err)
          }
        },
      },
    }
    </script>
    <style></style>
    
    
部署strapi
  • 需要支持 Node 的服务器

  • 数据库 – 建议 MySQL 或者 MongoDB写给前端的MySQL极简安装

  • strapi默认使用 sqlite 数据库,部署到线上时,不推荐使用

  • 切换数据库为mysqlconfigurations,将 config/database.js 替换为Mysql的配置

    module.exports = ({ env }) => ({
      defaultConnection: "default",
      connections: {
        default: {
          connector: "bookshelf",
          settings: {
            client: "mysql",
            host: env("DATABASE_HOST", "localhost"),
            port: env.int("DATABASE_PORT", 3306),
            database: env("DATABASE_NAME", "strapi"),
            username: env("DATABASE_USERNAME", "root"),
            password: env("DATABASE_PASSWORD", "329926"),
          },
          options: {},
        },
      },
    });
    
  • 修改 package.json,添加 mysql 依赖npm install mysql --save,如果不需要测试,则可以删除 sqlite3

  • 将项目上传到远程仓库后台项目地址

  • 在服务器上克隆上传到项目地址git clone https://github.com/wang1xiang/blog-backend.git

  • 安装依赖npm i,打包项目npm run build,启动项目npm run start

  • 可以使用pm2pm2 start npm -- run start --name blog-backend启动项目,可以让node应用跑到后台

  • 安装成功,打开http://106.75.190.29/admin访问并重新加载数据

本地GridSome应用连接远程服务器
  • 修改gridsome.config.js

    module.exports = {
      siteName: 'Gridsome',
      plugins: [
          ...
        {
          use: '@gridsome/source-strapi',
          options: {
            apiURL: 'http://106.75.190.29:1337',
            queryLimit: 1000, // Defaults to 100
            contentTypes: ['post', 'tag'], // StrapiPost配置集合
            // typeName: 'Strapi',
            singleTypes: ['general']
          },
        },
      ]
    }
    
  • apiURL可以使用环境变量的形式设定,配置环境变量,添加.env.development和.env.production

    // .env.development
    GRIDSOME_API_URL=http://106.75.190.29:1337
    // .env.production
    GRIDSOME_API_URL=http://106.75.190.29:1337
    // gridsome.config.js
    {
          use: '@gridsome/source-strapi',
          options: {
            apiURL: process.env.GRIDSOME_API_URL,
            ...
          },
        },
    

    可以配置不同的ip地址,修改apiURL为process.env.GRIDSOME_API_URL,重启

  • 此时可以正常访问网站,当时发现图片加载有问题,需要设置图片路径,当然不能直接在模板中写process.env.GRIDSOME_API_URL,使用mixin代替

    // main.js
     Vue.mixin({
        data() {
          return {
            GRIDSOME_API_URL: process.env.GRIDSOME_API_URL,
          }
        },
      })
    

    使用ip的地方替换为GRIDSOME_API_URL即可

  • 打开应用http://localhost:8080/访问成功,图片正常加载,此时已联通GridSome客户端和服务器的strapi

使用Vercel – 部署 Gridsome 应用

使用Vercel 进行静态应用项目的部署

基本使用

  • 使用gitHub登陆,选择Continue With GitHub

  • 登陆成功,选择new Project

  • 选择Import Git Repository,添加自己的git仓库,选择项目导入import

image-20210403163231079.png
  • 如果不需要修改build和环境变量,直接选择Deploy,等待Vercel部署打包完成

    image-20210403163433202.png
  • 等待部署成功,点击visit,即可访问生成的静态站点

    image-20210403165828150.png

配置自动构建

配置strapi,当数据改变时,触发Vercel自动构建

  • 选择项目blog-with-gridsome,选择settings --> git --> Deploy Hooks,设置部署钩子

    image-20210403171715153.png
  • 点击create Hook,将生成的钩子地址复制到strapi中
image-20210403171740795.png
  • 在strapi后台项目中,选择设置 --> Webhooks --> 创建新Hook,当数据更新操作时,请求webhooks,重新触发构建
image-20210403172046262.png
  • 此时回到vercel,打开项目的Deployments部署记录,修改数据,当前页面会自动刷新
image-20210403172805290.png
  • 等待部署完成,有时页面可能会有延时,需要手动刷新
使用GitHub Page+ GitHub Actions – 部署 Gridsome 应用
  • 在gridsome.config.js中修改pathPrefix为github项目路径

    pathPrefix: 'blog-with-gridsome/' #项目地址
    
  • 重新打包,取消.gitignore中的dist,将dist目录上传到远程分支

    git subtree push --prefix dist origin gh-pages
    
  • 项目对应的settings,GitHub Pages会自动加载gh-pages下的分支,点击上面的连接访问网站

    image-20210403180137115.png
  • 配置GitHub Actinos自动部署

    个人设置 --> Personal access tokens --> New personal access token

    img
  • github对应项目 --> Settings --> New Secrets,添加刚刚生成的token

    img
  • .github/workflows/deploy.yml

name: GitHub Actions Build and Deploy Demo
on:
  push:
    branches:
      - master
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master
    - name: Build and Deploy
      uses: JamesIves/github-pages-deploy-action@master
      env:
        ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
        BRANCH: gh-pages
        FOLDER: dist
        BUILD_SCRIPT: npm install && npm run build
  • 重新修改代码,并提交,可以看到actions中的状态

    image-20210403182638066.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容