# 折腾记录｜使用 Nuxt.js 重写个人主页，使用 SSR 优化 SEO ，实现一些期待已久的效果

原文：https://blog.grtsinry43.com/posts/home-website-refactoring-nuxtjs/

作者：grtsinry43

发布时间：2024-09-18T18:09:49Z

内容更新时间：2024-09-18T18:09:49Z

在 22 年刚创建个人主页的时候，由于我的技术水平不够，只能用一些 wordpress typecho 这种直观的工具，后来因为换域名等等一系列的问题就被搁置了...再后来为了过备案，用图形化工具和 bootstrap 糊了一个，而如今熟练掌握 Vue 和 React 等框架之后，也到了完全重写的时候了，于是就有了这段记录。

<!--more-->

关键词：Vue SSR，Nuxt.js

> 网页的开发一直在持续哇...本文章会持续跟进更新

最后链接：https://www.grtsinry43.com/

## 框架选择

首先，对于这种主页类的网站，单纯的 Vue React SPA（单页面应用）肯定是不行的，比如说这里我们使用 pure-admin 模板创建的一个后台系统（Vue3），对于搜索引擎来说不会等待页面完全加载和渲染，而抓到的这些内容无法分析网站的关键词和主题等，导致很难被搜索引擎排名考前，因此我们不得不从历史中汲取经验，从服务端生成静态网页，到了客户端渲染的单页面应用，又回到了服务端渲染

![这样是很难做好 SEO 的](https://dogeoss.grtsinry43.com/2024/08/screenshot_20240830_183309.png)

对于个人主页这种比较小的项目，我更喜欢使用 Vue 来完成开发，也就是其对应的 SSR 框架——Nuxt.js

## 快速上手

由于要使用 `NuxtUI`，我们便可以使用官方的创建工程的脚手架

```bash
npx nuxi@latest init -t ui [项目名称]
```

创建好项目之后，我们便可以像正常写 Vue 一样来进行开发啦

### 前置准备工作

在代码块以及项目名称中，我们选用一个等宽而且适用于代码的字体 `JetBrains Mono`，于是我们可以在 `assets/fonts.css` 中这样进行引入

```css
@font-face {
    font-family: 'JetBrains Mono';
    src: url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Medium.woff2') format('woff2');
    font-weight: 500;
    font-style: normal;
}

.font-jb-mono {
    /* 设置fallback防止出现宋体 */
    font-family: 'JetBrains Mono', 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Console', 'Microsoft YaHei', 'WenQuanYi Micro Hei', monospace;
}
```

注意这里要设置好 fallback 字体，因为某些 Windows 系统（雾）默认会回落到宋体，导致中文字体不堪入目

以后当我们需要使用的时候只需要挂上这个类名就好

### 防止掉头发

经典防止掉头发几件套：`assets/global.css`

```css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

a {
    text-decoration: none;
    color: inherit;
}

ul {
    list-style: none;
}
```

## 疑难解决方案与创新点

### 桌面与移动端导航栏适配

为了达到最完美的使用效果，我直接写了 `NavBar` 和 `NavBarMobile` 两个组件，通过简单的媒体查询来进行切换

```css
@media (max-width: 800px) {
  .nav-bar-desktop {
    display: none;
  }

  .nav-bar-mobile {
    display: block;
  }
}
```

对于移动段的 Navbar，我选择了可收起菜单来优化体验

```vue
const toggleMenu = () => {
  isMenuOpen.value = !isMenuOpen.value
}

const toggleLocale = () => {
  locale.value = locale.value === 'en' ? 'zh' : 'en';
};
</script>

<template>
  <div
      class="nav-container fixed w-full bg-opacity-80 bg-blue-50 text-blue-950 dark:bg-opacity-80 dark:bg-black dark:text-white">
    <UContainer class="flex flex-row items-center justify-between nav-inner">
      <div class="nav-logo">
        <NuxtLink :to="localePath('/')" class="font-bold">{{ $t('homePageTitle') }}</NuxtLink>
      </div>
      <!-- Toggle Button for Mobile Menu -->
      <UButton class="lg:hidden bg-opacity-0 dark:bg-opacity-0" @click="toggleMenu"
               icon="i-heroicons:bars-3-bottom-right"
               square
               color="gray"
      >
      </UButton>
      <!-- Theme Toggle Button -->
      <div class="theme-option lg:block">
        <UToggle
            on-icon="i-heroicons-moon-20-solid"
            off-icon="i-heroicons-sun-20-solid"
            :model-value="colorMode.preference === 'dark'"
            @change="toggleTheme"
        />
      </div>
      <!-- Icons Container -->
      <div class="actions-container hidden lg:grid">
        <Icon class="language-toggle-icon hover:text-blue-400 dark:hover:text-blue-600" name="i-heroicons-language" @click="toggleLocale"/>
        <Icon class="rss-icon hover:text-blue-400 dark:hover:text-blue-600" name="i-heroicons-rss"/>
        <a href="https://github.com/grtsinry43/home-web" target="_blank">
          <Icon class="github-icon hover:text-blue-400 dark:hover:text-blue-600" name="i-grommet-icons:github"/>
        </a>
      </div>
    </UContainer>

    <!-- Mobile Navigation Menu -->
    <div v-show="isMenuOpen"
         class="nav-extend-container flex flex-col items-center bg-blue-50 bg-opacity-85 dark:bg-black dark:bg-opacity-80 backdrop-blur-lg">
      这里就是菜单项目啦
    </div>
  </div>
</template>
```

###  深浅色模式的极不优雅解决方案

```js
import {onMounted} from "vue";
const colorMode = useColorMode();

const toggleTheme = () => {
  if (colorMode.preference === 'system') {
    colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark';
    buttonState.value = colorMode.preference === 'dark';
  } else {
    colorMode.preference = colorMode.preference === 'dark' ? 'light' : 'dark';
    buttonState.value = colorMode.preference === 'dark';
  }
};

const buttonState = ref(false);

onMounted(() => {
  // 根据最开始的主题设置按钮的图标
  buttonState.value = colorMode.value === 'dark';
})
```

这里深色模式的实现有点太不优雅了，我发现 NuxtUI 官网确实有一个跟随主题的开关，但是自己实现起来就是很奇怪，对于输出的 `colorMode` 有两个用的上的属性，一个是 `preference` 对应偏好主题（light dark system），一个是 `value` 对应当前主题（light dark），由于偏好能被保存下来，于是选择在切换按钮时改变 `preference` 的对应的值

当组件被挂载（onMounted），首先根据 value 设置初始的状态，当点击切换时，对于当前是否偏好系统要采取不同的方式来切换，不知道大佬们有没有什么好办法，或者过几天我再看看 Nuxt 源码
