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
- 安装
npm install -D tailwindcss
npx tailwindcss init
- tailwind.config.js
content: ["./pages/**/*.vue", "./components/**/*.vue"],
- plugins\tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
- package.json 可watch, 吃电脑性能,不用样式可不watch
"scripts": {
"css": "tailwindcss -i ./plugins/tailwind.css -o ./plugins/tailwindcss.css --watch",
}
nuxt/content用法
https://content.nuxt.com/get-started/installationnpm install @nuxt/content
nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/content'
],
content: {
// ... options
}
})
实际使用配置
- 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自动部署
- 新建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.com 和 https://ackuikui.com 并行加载-共享一个tcp
- chrome并发6个tcp连接
- http1 keep-alive 共享一个tcp, 但是串行加载