nuxt博客

为什么用nuxt,

  • 相比vuepress,nuxt界面功能自定义好操作些,可生成目录,后续只需要更新md文件即可,
  • 只是想要个vue官网那种一样风格的, 就用vuepress相对比较简单,
  • 不考虑nuxt-ui-pro, 可用ui框架实现一些ssr项目,

nuxt用法

https://nuxt.com/docs/getting-started/installation

npx nuxi init -t ui my-site

也可以不使用模板,手动安装
npm i nuxt
mkdir pages
nuxt dev启动

nuxt-ui用法

nuxt-ui-pro build时要付费挺贵,这里不使用
https://ui.nuxt.com/getting-started/installation

tailwindcss用法

https://tailwindcss.com/docs/installation

  1. 安装
npm install -D tailwindcss
npx tailwindcss init
  1. tailwind.config.js
content: ["./pages/**/*.vue", "./components/**/*.vue"],
  1. plugins\tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. package.json 可watch, 吃电脑性能,不用样式可不watch
  "scripts": {
    "css": "tailwindcss -i ./plugins/tailwind.css -o ./plugins/tailwindcss.css --watch",
  }

nuxt/content用法

https://content.nuxt.com/get-started/installation
npm install @nuxt/content
nuxt.config.ts

export default defineNuxtConfig({
  modules: [
    '@nuxt/content'
  ],
  content: {
    // ... options
  }
})

实际使用配置

  1. tailwind.config.js
export default {
  // 申明这些vue文件使用tailwind解析
  content: ["./pages/**/*.vue", "./components/**/*.vue"],
 
  theme: {
    extend: {
      // 排版插件-修改md生成文本样式
      // 参考文档 https://github.com/tailwindlabs/tailwindcss-typography 
      typography: ({ theme }) => ({
        // 参考 https://content.nuxt.com/ 使用的primary自定义的背景色和边框
        // 使用时 class="prose prose-kk", 默认排版名prose不调整
        kk: {
          css: {
            '--tw-prose-pre-bg': 'rgb(var(--color-gray-50))',
            '--tw-prose-pre-border': 'rgb(var(--color-gray-200))',

            // DEFAULT
            'max-width': "80ch", // 80字符0
          }
        },

        // 修改默认样式 pre带边框,code加内边距和圆角-不要`符号包裹
        // 取自tailwind.config.d.ts(content.nuxt官网源码编译)
        DEFAULT: {
          css: {
            "h1, h2, h3, h4": {
              fontWeight: "700",
              "scroll-margin-top": "var(--scroll-mt)",
            },
            "h1 a, h2 a, h3 a, h4 a": {
              borderBottom: "none !important",
              color: "inherit",
              fontWeight: "inherit",
            },
            a: {
              fontWeight: "500",
              textDecoration: "none",
              borderBottom: "1px solid transparent",
            },
            "a:hover": { borderColor: "var(--tw-prose-links)" },
            "a:has(> code)": { borderColor: "transparent !important" },
            "a code": {
              color: "var(--tw-prose-code)",
              border: "1px dashed var(--tw-prose-pre-border)",
            },
            "a:hover code": {
              color: "var(--tw-prose-links)",
              borderColor: "var(--tw-prose-links)",
            },
            pre: {
              borderRadius: "0.375rem",
              border: "1px solid var(--tw-prose-pre-border)",
              color: "var(--tw-prose-pre-code) !important",
              backgroundColor: "var(--tw-prose-pre-bg) !important",
              whiteSpace: "pre-wrap",
              wordBreak: "break-word",
            },
            code: {
              backgroundColor: "var(--tw-prose-pre-bg)",
              padding: "0 0.375rem",
              display: "inline-block",
              borderRadius: "0.375rem",
              border: "1px solid var(--tw-prose-pre-border)",
            },
            "code::before": { content: '""' },
            "code::after": { content: '""' },
            "blockquote p:first-of-type::before": { content: "" },
            "blockquote p:last-of-type::after": { content: "" },
            'input[type="checkbox"]': {
              color: "rgb(var(--color-primary-500))",
              borderRadius: "0.25rem",
              borderColor: "rgb(var(--color-gray-300))",
              height: "1rem",
              width: "1rem",
              marginTop: "-3.5px !important",
              marginBottom: "0 !important",
              "&:focus": { "--tw-ring-offset-width": 0 },
            },
            'input[type="checkbox"]:checked': {
              borderColor: "rgb(var(--color-primary-500))",
            },
            'input[type="checkbox"]:disabled': {
              opacity: 0.5,
              cursor: "not-allowed",
            },
            "ul.contains-task-list": { marginLeft: "-1.625em" },
            "ul ul": { paddingLeft: "1.5rem" },
            "ul ol": { paddingLeft: "1.5rem" },
            "ul > li.task-list-item": { paddingLeft: "0 !important" },
            "ul > li.task-list-item input": { marginRight: "7px" },
            "ul > li.task-list-item > ul.contains-task-list": {
              marginLeft: "initial",
            },
            "ul > li.task-list-item a": { marginBottom: 0 },
            "ul > li.task-list-item::marker": { content: "none" },
            "ul > li > p": { margin: 0 },
            "ul > li > span.issue-badge, p > span.issue-badge": {
              verticalAlign: "text-top",
              margin: "0 !important",
            },
            "ul > li > button": { verticalAlign: "baseline !important" },
            table: { display: "block", overflowX: "auto" },
            "table code": { display: "inline-flex" },

          },
        },

      }),
    },
  },
  plugins: [
    // require('@tailwindcss/typography'),
  ],
}

nuxt.config.ts

export default defineNuxtConfig({

  devtools: { enabled: true },
  
  modules: ['@nuxt/ui', '@nuxt/content'],

  nitro: {
    // 输出前后端
    output: { dir: 'dist', serverDir: 'dist/server', publicDir: 'dist/nuxt-blog' }
  },
  content: {
    highlight: {
      // https://github.com/shikijs/textmate-grammars-themes/tree/main/packages/tm-themes
      // theme: 'github-light',// D73A49
      // OR
      theme: {
        // https://content.nuxt.com/ 使用的 material-theme-lighter material-theme material-theme-palenight
        default: 'material-theme-lighter',
        // Theme used if `html.dark`
        dark: 'material-theme',
        // Theme used if `html.sepia`
        sepia: 'material-theme-palenight'
      }
    }
  }
})

ContentList 获取文件夹的文件路由

    <ContentList path="/article" v-slot="{ list }">
      <NuxtLink
        v-for="article in list"
        :key="article._path"
        :to="article._path"
        class="max-w-screen-xl mx-auto border p-4 rounded block my-4"
      >
        <h2 class="text-gray-800 font-semibold">{{ article.title }}</h2>
        <p class="text-slate-400">{{ article.description }}</p>
      </NuxtLink>
    </ContentList>

fetchContentNavigation获取目录下文件名-路由,
build丢失问题-这里同时使用一次ContentList并隐藏元素

const { data: navigation } = await useAsyncData("navigation", () =>
  fetchContentNavigation()
);
const list = navigation._rawValue[1].children;

queryContent 获取md文件的##h2等导航

const route = useRoute()

const { data: page } = await useAsyncData(route.path, () => queryContent(route.path).findOne())

<template>
    模板中使用
    {{page.body.toc.links}}
</template>

app.vue 定义一些变量

body{
  --header-height: 6rem;
  --scroll-mt: 80px;
}

CI/CD

提交代码到github,使用actions自动部署

  1. 新建workflow,
  • on 触发条件(这里main分支提交就自动执行)
  • jobs这里命名一个build任务
  • step使用一些插件 checkout,虚拟机下载代码
    setup-node, 安装指定node版本
    run, 虚拟机中build
    scp-action, dist文件夹上传到服务器 (该仓库setting中设置secrets即这里ssh账号密码,后端项目可设置env)

main.yml文件:

name: deploy_my_site
on:
  push:
    branches: [main]

jobs: 
  build:
    runs-on: ubuntu-latest 
    strategy:
      matrix:
        node_version: ['18.x'] # nuxt3 use node 18
        
    steps:
      - uses: actions/checkout@v4 
      
      - name: Use Node.js ${{ matrix.node_version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node_version }}
          
      - name: npm install, build
        run: |
          npm install
          npm run generate
          
      - name: SCP Files
        uses: appleboy/scp-action@v0.1.7 # not v0.0.1
        with:
          host: ${{secrets.REMOTE_HOST}}
          username: ${{secrets.REMOTE_USERNAME}}
          password: ${{secrets.REMOTE_PASSWORD}}
          port: 22
          source: "dist/nuxt-blog"
          target: ${{secrets.REMOTE_TARGET}}
          strip_components: 2 # /scp-action/issues/50

可选项:上传后用户不对,补充用户root或nginx, 组nginx,

chown -R root:nginx /usr/share/nginx/html

开启gzip

nuxt content 纯文本json,太多key重复了,开启压缩能提升90%
gzip开到最高,当前网站访问不高,暂时不升级cpu

nginx.conf

http {
...
    gzip  on;
    gzip_min_length  10k; 
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
}

开启http2

条件 https
https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2

nginx -V
OpenSSL version >= 1.0.2, 版本低的升级ssl
nginx version >= 1.25.1 , 使用http2 on, 低版本 ssl http2

chrome插件 HTTP/2 and SPDY indicator 验证不同网站支持的http版本
访问测试,http://ackuikui.comhttps://ackuikui.com 并行加载-共享一个tcp

  • chrome并发6个tcp连接
  • http1 keep-alive 共享一个tcp, 但是串行加载