0


Vue3+Nuxt3 从0到1搭建官网项目,SEO搜索、中英文切换、图片懒加载

Vue2+Nuxt2 从 0 到1 搭建官网~

Vue3+Nuxt3 从0到1搭建官网项目

想开发一个官网,并且支持SEO搜索,当然离不开我们的 Nuxt ,Nuxt2 我们刚刚可以熟练运用,现在有出现了Nuxt3,那通过本篇文章让我们一起了解一下。

安装 Nuxt3,创建项目

安装nuxt3, 需要node v18.10.0以上,大家记得查看自己的node版本。

升级node,可以参考使用nvm 切换不同node版本~

// node  v18.10.0// npx nuxi@latest init <project-name>

npx nuxi@latest init nuxt3-demo

cd nuxt3-demo

初始化的 package.json

这是项目刚创建后的package.json文件

{"name":"nuxt3-demo","private":true,"type":"module","scripts":{"dev":"nuxt dev","build":"nuxt build","generate":"nuxt generate","preview":"nuxt preview","postinstall":"nuxt prepare"},"dependencies":{"nuxt":"3.8.2","vue":"3.3.10","vue-router":"4.2.5"}}

项目结构

├── app.vue // 主文件
├── assets // 静态资源
├── components  // 公共vue组件
├── composables // 将你的Vue组合式函数自动导入到你的应用程序中
├── error.vue  // 路由匹配不到时
├── i18n.config.ts  // 语言切换配置文件
├── lang  // 语言JSON
├── nuxt.config.ts  // nuxt 配置文件
├── package.json  
├── pages   //  pages文件夹下面的页面名,默认为 路由地址
├── plugins  // 公共插件
├── public// 提供网站的静态资源
├── server  
├── tsconfig.json 
└── yarn.lock // 包含了应用程序的所有依赖项和脚本

初始化项目

我们首先创建一个首页,将项目运行起来,这样一会儿讲到SEO、语音切换时,方便查看效果

pages 文件下创建index.vue

<template><div class="home-wrap">
    Nuxt3--这是我们的首页
  </div></template><script>exportdefault{}</script><style lang="scss" scoped>.home-wrap{height: 500px;display: flex;
  justify-content: center;
  align-items: center;
  font-size: 50px;}</style>

我们发现页面出现如下错误,提示我们需要引入 sass
在这里插入图片描述
在这里插入图片描述)

引入sass

// 如果下载失败。记得比对package.json 中依赖的版本号// node_modules 和 yarn.lock 记得也删除一下
yarn add [email protected] [email protected] [email protected]

修改 app.vue 文件

<template><div><NuxtPage /></div></template>

查看效果

在这里插入图片描述

配置公共的css、meta

在我们的项目中,UI风格肯定是有规范(统一)的,因此我们可以将 css 重置文件公共的css文件,以及Meta提前引入

assets下的css

在assets 文件夹下,我们可以创建css(样式)、img(图片)、fonts(字体库)等文件夹

reset.scss 重置文件

因为UI主体区域为1200px,因此body 我们设置了最大宽度是1350px,这样主体区域两侧有一点占位空间,使内容不至于紧贴设备边界

/* CSS reset */// /assets/css/reset.scss

html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {margin:0;padding:0;}

body{
    font-family:"思源黑体","Microsoft YaHei","微软雅黑",Arial,sans-serif;
  font-size: 14px;color: #333;
  min-width: 1350px;}*{-webkit-text-size-adjust: none;}*,*:after,*:before{
    box-sizing:border-box;margin:0;padding:0;}
table {
    border-collapse:separate;
    border-spacing:0;}
fieldset,img {border:0;}
img{display: block;-webkit-user-drag: none;}
img[src=""],img:not([src]){opacity:0;border:none;visibility: hidden;
    max-width: none;}
address,caption,cite,code,dfn,th,var{
    font-style:normal;
    font-weight:normal;}
ol,ul ,li{
    list-style:none;}
caption,th {
    text-align:left;}
h1,h2,h3,h4,h5,h6 {
    font-size:100%;
    font-weight:normal;}
abbr,acronym {border:0;}
a{
    text-decoration:none;}/* 解决兼容而加的样式 */
a, img {-webkit-touch-callout: none;/*禁止长按链接与图片弹出菜单*/}
a,button,input{-webkit-tap-highlight-color:rgba(255,0,0,0);}
img{display: block;}
button,input,optgroup,select,textarea {outline:none;/*-webkit-appearance:none; /*去掉webkit默认的表单样式*/}

a,button,input,optgroup,select,textarea {-webkit-tap-highlight-color:rgba(0,0,0,0);/*去掉a、input和button点击时的蓝色外边框和灰色半透明背景*/}input:-ms-input-placeholder{color:#b3b7c0;}input::-webkit-input-placeholder{color:#b3b7c0;}input:-moz-placeholder{color:#b3b7c0;}input::-moz-placeholder{color:#b3b7c0;}

common.scss

/* CSS common */// 这里大家可以写一些公共的css样式,我这里主要是举例  // /assets/css/common.scss.g-border{position: relative;}.g-border1{position: relative;}.g-border:after{content:'';position: absolute;bottom:0;width:100%;height:1px;background:#e8e8e8;overflow: hidden;left:0;transform:translate(0%,0)scale(1,0.5);}.g-border1::before{content:'';position: absolute;top:0;width:100%;height:1px;background:#e8e8e8;overflow: hidden;left:0;transform:translate(0%,0)scale(1,0.5);}.g-border-on::before,.g-border-on:after{background: #dcdcdc!important;}.g-text-ove2{display:-webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp:2;overflow: hidden;}.g-text-ove1{overflow: hidden;text-overflow:ellipsis;white-space: nowrap;}

配置nuxt.config.ts

// 熟悉我的小伙伴可能注意到,我非常喜欢在项目中使用 address,哈哈哈
yarn add [email protected]
// https://nuxt.com/docs/api/configuration/nuxt-configconst address =require('address')const localhost = address.ip()||'localhost'exportdefaultdefineNuxtConfig({ssr:true,app:{head:{meta:[{charset:'utf-8'},{name:'viewport',content:'width=device-width, initial-scale=1'},{hid:'viewport',name:'viewport',content:"width=1350,  user-scalable=no,viewport-fit=cover"},{hid:'description',name:'description',content:'CSDN 作者:拿回忆下酒,介绍Vue3+Nuxt3 从0到1搭建官网项目(SEO搜索、中英文切换、图片懒加载)的dome'},{hid:'keywords',name:'keywords',content:'Vue3,Nuxt3,CSDN 拿回忆下酒'},{name:'format-detection',content:'telephone=no'}],link:[{rel:'icon',type:'image/x-icon',href:'/favicon.ico'}]},},css:['@/assets/css/reset.scss',// 公共class'@/assets/css/common.scss'],devtools:{enabled:true,ssr:false},devServer:{host: localhost,port:8303}})

现在的package.json

防止小伙伴下错版本号,咱们确定一下 现在的依赖包的版本号

大家也可以复制一下内容,将node_modulesyarn.lock 删除,重新执行 yarn install

{"name":"nuxt3-demo","private":true,"type":"module","scripts":{"dev":"nuxt dev --open","build":"nuxt build","generate":"nuxt generate","preview":"nuxt preview","postinstall":"nuxt prepare"},"dependencies":{"@nuxt/devtools":"latest","@nuxtjs/i18n":"8.0.0","address":"2.0.1","nuxt":"3.8.2","sass":"1.69.5","sass-loader":"13.3.2","string-width":"7.1.0","vue":"3.3.10","vue-router":"4.2.5"}}

查看效果

我们可以看到 IP、端口、meta、css 已经都改变了
在这里插入图片描述

创建新页面

创建新页面,演示目录结构路由跳转多页面TDK配置等等

pages目录结构

我们在pages文件夹下,新建 首页(index)、关于我们(about)、订单(product/order)、管家(product/steward)等页面,用来演示 目录结构路由对应关系

// pages 目录结构

├── nuxt.config.ts
├── package.json
├── pages
│   ├── abouut.vue
│   ├── index.vue
│   └── product
│       ├── order.vue
│       └── steward.vue

components 创建组件

components/ 目录是你放置所有 Vue 组件的地方。我们可以把公共组件放到这里

Header 组件

<template><section class="c-head-wrap"><div class="content-box"><div  class="head-box"><section class="main g-width-box g-cen-y"><div class="logo-box">
            演示logo
            <!--<img class="img" src="@/assets/img/logo-top.png"/>--></div><div 
            class="nav-btn-box "><div class="g-dis"><div
                v-for="(m, i) in navArr":key="i"class="btn"
                ref="btnId"
                @click="routerFn(m)":class="[isPageFn(m,i)]"><h6>{{ m.name }}</h6><div v-show="m.children"class="children-box"><div 
                    class="item" 
                    v-for="(n,ind) in m.children":key="i+'-'+ind" 
                    @click.stop="routerFn(n)">{{ n.name }}</div></div></div></div><div class="border":style="isStyle"></div></div></section></div></div></section></template><script setup>import{ ref }from"vue";const router =useRouter()const route =useRoute()const navArr =ref([{path:"/",name:'首页'},{path:"/product",name:'产品业务',children:[{path:"/product/order",name:'订单'},{path:"/product/steward",name:'管家'}]},{path:"/about",name:'关于我们'}]);let btnId =ref(null)let isStyle =ref('')constrouterFn=(e)=>{if(e.children)return 

  router.push(e.path)};constisPageFn=(e,ind)=>{let async =falseif(route.path == e.path){
    async =true}elseif(e.children){
    async =!!e.children.filter(m=> m.path == route.path).length
  }return async ?'on':''}</script><style lang="scss" scoped>.c-head-wrap{
  min-width: 1350px;position: absolute;left:0;width:100%;
  z-index:22;}.content-box{width:100%;height: 70px;background: transparent;
  border-bottom: 1px solid #E0E4E8;}.main{height: 70px;}.logo-box{height:100%;display: flex;
  align-items: center;
  margin-right: 102px;
  font-size: 20px;
  font-weight:500;}.nav-btn-box{flex:1;position: relative;.border{width: 72px;height: 2px;background: #0044FF;position: absolute;bottom:0;transition: left 0.28s;opacity:0;}.btn{
    font-weight:500;
    font-size: 18px;color: #4F587F;
    line-height: 70px;padding:0 18px;cursor: pointer;position: relative;
    font-weight:400;
    z-index:2;&.on{color: #0044FF;position: relative;}&:hover{.children-box{display: block;}}}}.children-box{display: none;position: absolute;
  background-image: linear-gradient(197deg,rgba(246,246,246,1),rgba(246,246,246,.65));
  backdrop-filter:blur(5px);
  box-shadow:0 20px 40px 0rgba(74,91,130,.16);border: 2px solid #fff;
  
  min-width:100%;left:50%;top: 70px;
  line-height: 50px;
  text-align: center;
  white-space: nowrap;transform:translate(-50%,0);
  font-size: 16px;
  font-weight:400;color: #4F587F;
  z-index:2;.item{
    border-bottom: 1px solid #E0E4E8;padding:0 14px;&:last-child{
      border-bottom:0;}&.on,&:hover{color: #0044FF;}}}</style>

Footer 组件

<template><div class="footer-box"><div class="box g-width-box"><div class="left"><div class="icon">演示logo</div><div class="phone">咨询电话:+86-136xxxx8899</div><div class="btn-box g-cen-y"><span>版权 © 演示xxxx科技有限公司</span><i></i><a href="https://beian.miit.gov.cn/" target="_blank"class="btn">京ICP备2010xxxx88号</a><i></i><a href="https://beian.mps.gov.cn/#/query/webSearch?code=1010xxxxx3434" target="_blank"class="btn sprite-btn"><img class="sprite" src="assets/img/sprite.png"/><span>京公网安备1010xxxxx3434号</span></a></div></div><div class="right"></div></div></div></template><style lang="scss" scoped>.footer-box{height: 200px;background: #242933;.box{display: flex;
    justify-content:space-between;
    padding-top: 40px;.left{
      font-size: 14px;.icon{height: 32px;display: block;
        margin-bottom: 40px;color:#fff;
        font-size: 30px;}.phone{
        line-height: 22px;color: #FFFFFF;}.btn-box{
        line-height: 24px;color:#878FB4 ;&>i{height: 14px;width: 2px;background: #878FB4 ;margin:0 10px;}.btn{color:#878FB4 ;
          font-size: 14px;cursor: pointer;&:hover{color: #fff;}}.sprite-btn{display: flex;
          align-items: center;}.sprite{width: 16px;height: 17px;
          margin-right: 5px;}}}.right{display: flex;
      flex-direction:column; 
      align-items: center;.qrcode1{width: 100px;height: 100px;}.txt{
        line-height: 32px;
        font-size: 12rpx;color: #E9EDFF;
        text-align: center;}}}}</style>

引入组件

官网一般header(头部)、footer(底部)都是复用的,因此我们在app.vue文件中引用它们

// app.vue<template><div><Header /><NuxtPage /><Footer /></div></template>

组件效果

在这里插入图片描述

多语音系统

现在好多公司的官网都是多语言,因此我们一起了解一下 i18n

引入i18n

yarn add @nuxtjs/[email protected]

新建lang文件夹

这是一个自创建的文件夹,用来存放 语言切换 需要用到的数据,我们来了解一下它的目录结构

├── nuxt.config.ts
├── i18n.config.ts
├── lang
│   ├── cn
│   │   ├── about.js
│   │   ├── home.js
│   │   ├── index.js // 入口文件
│   │   ├── order.js
│   │   └── steward.js
│   └── en
│       ├── about.js
│       ├── home.js
│       ├── index.js  // 入口文件
│       ├── order.js
│       └── steward.js

入口文件(index.js )

index.js 的代码是一致的,主要作用是 将同一种语言下的文件合并到一起, 我以其中一个为例

// 获取同一目录下 的 js 文件const files =import.meta.globEager('./*.js');const modules = Object.keys(files).reduce((prev, cur)=>{// 获取js 文件的 文件名 并转  大写 (这里很重要!!!!)// 这里  大写的文件名,是日后 dom 中日后替换的关键字let key = cur.split('/')[1].split('.')[0].toUpperCase()// 将内容合并在一起,为了方便理解,下面有合并后的JSON图return{...prev,...{[key]:files[cur]?.default}}},{});exportdefault{...modules};

中英js文件 对比

每个js文件,JSON结构都是一致的,我们以home、about 为例对比一下

// cn/home.jsexportdefault{'b1':{'title':'我们的首页','txt':'这是一段尝试,json结构可以任意定义,只要保持统一规范即可'}}// en/home.jsexportdefault{'b1':{'title':'Our homepage','txt':'China\'s leading fintech platform for automobiles empowering supply chain ecosystem partners. '}}***********************// cn/about.jsexportdefault{'b1':{'title':'关于我们'}}// en/about.jsexportdefault{'b1':{'title':'About Us'}}

合并后的JSON图

在这里插入图片描述

i18n.config.ts

import en from'./lang/en/index.js'import cn from'./lang/cn/index.js'exportdefaultdefineI18nConfig(()=>({legacy:false,// 是否兼容之前fallbackLocale:'cn',// 区配不到的语言就用enmessages:{
        en,
        cn
    }}))

nuxt.config.ts

exportdefaultdefineNuxtConfig({...modules:['@nuxtjs/i18n'],i18n:{strategy:'prefix_and_default',// 添加路由前缀的方式locales:["en","cn"],//配置语种defaultLocale:'cn',// 默认语种vueI18n:'./i18n.config.ts',// 通过vueI18n配置},...})

修改index.vue 文件

我们以index.vue 文件为例,主要看 **$t(

HOME.b1.title

)** 部分,HOME 便是当时 lang中的文件名

<template><div class="home-wrap">
    Nuxt3--{{$t(`HOME.b1.title`)}}</div></template><script setup>exportdefault{}</script><style lang="scss" scoped>.home-wrap{
  min-height:calc(100vh - 200px);display: flex;
  justify-content: center;
  align-items: center;
  font-size: 50px;}</style>

查看效果

中英文切换,主要是通过不同的路由进行区分的,大家注意看浏览器URL地址

中文效果

在这里插入图片描述

英文效果

在这里插入图片描述

动态切换语言

我们虽然实现了语言替换,但是官网中并没有实现语言动态切换的功能,因此我们需要进一步完善,首先我们需要引入pinia

yarn add @pinia/[email protected] [email protected]

nuxt.config.ts

// 添加  @pinia/nuxtexportdefaultdefineNuxtConfig({...modules:['@pinia/nuxt','@nuxtjs/i18n']...})

head文件

lang 文件夹下增加 head.js 文件,用来替换 头部的语言

// lang/cn/head.jsexportdefault{'home':'首页','product':'产品业务','order':'订单','steward':'管家','aboutUs':'关于我们'}// lang/en/head.jsexportdefault{'home':'Home','product':'Product','order':'Order','steward':'Steward','aboutUs':'About Us'}

composables

使用composables/目录将你的Vue组合式函数自动导入到你的应用程序中。


├── components
├── composables
│   ├── global.ts
│   └── store.ts

store.ts

exportconst useStateStore =defineStore('nuxtStore',()=>{const locale =ref('')let localeName =ref('')const localeArr =ref([{id:'cn',name:'中文'},{id:'en',name:'English'}])constsetState=(name: any)=>{let arr = localeArr.value.filter(m=>m.id ==name)let obj = localeArr.value[0]if(arr.length>0){
      obj = arr[0]}

    locale.value = obj.id
    localeName.value = obj.name
    
  }return{
    locale,
    localeArr,
    localeName,
    setState
  }})

global.ts

exportdefaultfunction(){constrouterFn=(fullPath: any)=>{const route =useRoute()const localeRoute =useLocaleRoute()const store =useStateStore()// 你的公共逻辑let arr  =['cn','en']let a = arr.filter((m)=> fullPath.includes(m))if(a.length){let paths = fullPath.split(a[0])
      fullPath ='/'+store.locale +paths[1]}else{
      fullPath ='/'+store.locale +fullPath
    }// 用于把当前页面生成对应的语言前缀的路由,例如:/zh/,/zh/aboutconst routePath =localeRoute({path:fullPath,query:{...route.query }})if(routePath){returnnavigateTo(fullPath)// 路由跳转}};return{
    routerFn
  };}

header.vue

我们需要重新改造一下header文件,右上角增加了语言切换按钮,整个导航栏 增加了滚动超出页面后悬浮,按钮切换高亮动画效果等

<template><section class="c-head-wrap":class="[{'on':scrollY >=70},store.isIPhone?'m':'pc']"><div class="content-box"><div class="back-box"></div><div  class="head-box"><section class="main g-width-box g-cen-y"><div class="logo-box"><img class="img" src="@/assets/img/logo-top.png"/></div><div 
            class="nav-btn-box "><div class="g-dis"><div
                v-for="(m, i) in navArr":key="i"class="btn"
                ref="btnId"
                @click="routerFn(m)":class="[isPageFn(m,i),{'hover':isChildren == m.path}]"><h6>{{$t(`HEAD.${m.name}`)}}</h6><div v-show="m.children && async"class="children-box"><div 
                    class="item" 
                    v-for="(n,ind) in m.children":key="i+'-'+ind":class="[isPath == n.path && 'on']"
                    @click.stop="routerFn(n)">{{$t(`HEAD.${n.name}`)}}</div></div></div></div><div class="border":style="isStyle"></div></div><div 
            class="locale-box g-cen-y" 
            @click="localeFn":class="[{'hover':isChildren == 'locale'}]"><i class="icon1"></i><span>{{ store.localeName}}</span><i class="icon2"></i><div  class="children-box" v-show="async "><div 
                  class="item" 
                  v-for="n in store.localeArr":key="n.id":class="[store.locale == n.id && 'on']"
                  @click.stop="change(n.id)">{{ n.name }}</div></div></div></section></div></div></section></template><script setup>import{ ref }from"vue";import  useGlobal from'@/composables/global.ts'const route =useRoute()const global =useGlobal()const store =useStateStore()defineProps({scrollY:{type:[String,Number],default:0}});let async =ref(true)const navArr =ref([{path:"/",name:'home'},{path:"/product",name:'product',children:[{path:"/product/order",name:'order'},{path:"/product/steward",name:'steward'}]},{path:"/about",name:'aboutUs'}]);let btnId =ref(null)let isStyle =ref('')let isChildren =ref('')let isPath =computed(()=>{let name ='cn'if(route.path.split('/')[1]){
    name = route.path.split('/')[1]}if(name==404){
    name ='cn'}

  store.setState(name)return route.path.split(store.locale)[1]||'/'})constrouterFn=(e)=>{if(e.children){if(store.isIPhone){
      isChildren.value = e.path
    }return}let fullPath =  route.path.split( store.locale )[1]// 去重  路径无变化,不需要跳转if(fullPath === e.path)return

  global.routerFn(e.path)asyncFn()};constlocaleFn=()=>{if(store.isIPhone){
    isChildren.value ='locale'}}constisPageFn=(e,ind)=>{let async =falseif(isPath.value == e.path){
    async =true}elseif(e.children){
    async =!!e.children.filter(m=> m.path == isPath.value).length
  }let arr = btnId.value
  if(async && arr){let w =0;for(let i=0;i<ind;i++){
      w += arr[i].offsetWidth
    }

    isStyle.value =`left:${w}px;opacity:1;width:${arr[ind].offsetWidth}px`}return async ?'on':''}// 切换中英文constchange=(e)=>{
  store.setState(e)let fullPath = route.fullPath
  global.routerFn(fullPath)asyncFn()}constasyncFn=()=>{
  async.value =false;
  isChildren.value =''setTimeout(()=>{
    async.value =true;},100)}</script><style lang="scss" scoped>.c-head-wrap{
  min-width: 1350px;position: absolute;left:0;width:100%;
  z-index:22;&.on{height:0;animation: boxShow .5s forwards ;position: sticky;.content-box{
      box-shadow:0 20px 40px 0rgba(74,91,130,.16);position: relative;}.head-box{position: relative;
      z-index:2;}.back-box{position: absolute;left:0;top:0;width:100%;height: 70px;display: block;background:rgba(255,255,255,.9);
      backdrop-filter:blur(3px);}}&.pc{.nav-btn-box .btn:hover,.locale-box:hover{.children-box{display: block;}}}&.m{.nav-btn-box .btn.hover,.locale-box.hover{.children-box{display: block;}}}}.content-box{width:100%;height: 70px;background: transparent;
  border-bottom: 1px solid #E0E4E8;.back-box{display: none;}}.main{height: 70px;}.logo-box{height:100%;display: flex;
  align-items: center;
  margin-right: 102px;.img{width: 300px;height: 32px;
    object-fit: cover;}}.nav-btn-box{flex:1;position: relative;.border{width: 72px;height: 2px;background: #0044FF;position: absolute;bottom:0;transition: left 0.28s;opacity:0;}.btn{
    font-weight:500;
    font-size: 18px;color: #4F587F;
    line-height: 70px;padding:0 18px;cursor: pointer;position: relative;
    font-weight:400;
    z-index:2;&.on{color: #0044FF;position: relative;}}}.locale-box{
  font-weight:500;
  font-size: 18px;color: #1C1E20;height:100%;display: flex;
  align-items: center;cursor: pointer;position: relative;.icon1{width: 22px;height: 22px;
    margin-right: 9px;background:url('@/assets/img/icon1.png') no-repeat center;
    background-size: cover;}.icon2{width: 24px;height: 24px;background:url('@/assets/img/icon2.png') no-repeat center;
    background-size: cover;}}.children-box{display: none;position: absolute;
  background-image: linear-gradient(197deg,rgba(246,246,246,1),rgba(246,246,246,.65));
  backdrop-filter:blur(5px);
  box-shadow:0 20px 40px 0rgba(74,91,130,.16);border: 2px solid #fff;
  
  min-width:100%;left:50%;top: 70px;
  line-height: 50px;
  text-align: center;
  white-space: nowrap;transform:translate(-50%,0);
  font-size: 16px;
  font-weight:400;color: #4F587F;
  z-index:2;.item{
    border-bottom: 1px solid #E0E4E8;padding:0 14px;&:last-child{
      border-bottom:0;}&.on,&:hover{color: #0044FF;}}}

@keyframes boxShow {0%{top:-70px;}100%{top:0px
  }}</style>

error.vue

和 app.vue 同一级,增加error.vue 文件

<template><div /></template><script>exportdefault{created(){this.$router.replace('/404')}}</script><style></style>

404.vue

和index.vue同一级,增加404.vue 文件,文件内容可以自行编辑。 我这里直接重置到了首页

<template><div></div></template><script setup>import{ onMounted }from"vue";// const route = useRoute()onMounted(()=>{navigateTo('/')})</script>

查看效果

内容有限,我们以首页为例,对比一下 中英文切换效果

中文效果

在这里插入图片描述

英文效果

在这里插入图片描述

配置每个页面的TDK

TDK指的是搜索引擎优化(SEO)中的页面Title、Description、Keywords

  • Title:
  1. 页面的主要内容,对搜索引擎而言至关重要。
  2. 简洁、准确地描述页面内容。
  3. 不超过70个汉字或160个英文字符。
  • Description:
  1. 简要描述页面内容,提供页面主要信息。
  2. 不超过200个汉字或~300个英文字符。
  • Keywords:
  1. 关键词应该是与页面内容相关的,用于提升搜索结果的相关性。
  2. 不超过1000个汉字或~1600个英文字符。
  3. 使用对搜索引擎友好的逗号、分号或空格分隔关键词。

plugins 文件夹

Nuxt拥有一个插件系统,可以在创建Vue应用程序时使用Vue插件和其他功能。

在plugins 文件夹下,增加global.ts

global.ts

//global.tsexportdefaultdefineNuxtPlugin((nuxtApp)=>{return{provide:{setHead:(page: String)=>{const store =useStateStore()let{title,keywords,description}= store.headJson[store.locale][page]return{
            title,meta:[{hid:'keywords',name:'keywords',content: keywords},{hid:'description',name:'description',content: description}]}}}};})

index.vue

在首页中引用 global.ts 中 创建的 $setHead 方法

<template><div class="home-wrap"><div >
      Nuxt3--{{$t(`HOME.b1.title`)}}</div><div>{{$t(`HOME.b1.txt`)}}</div></div></template><script setup>const nuxtApp =useNuxtApp();useHead({...nuxtApp.$setHead('home')})</script><style lang="scss" scoped>......</style>

演示效果

中文效果

在这里插入图片描述

英文效果

在这里插入图片描述

图片懒加载

官网项目一直有一个弊端,就是项目中 图片资源比较多,导致打开速度慢。因此我们需要对图片资源做一下优化。

常规的优化一般针对图片压缩、图片格式、图片标签属性 和 图片懒加载

我们这里主要讨论一下 ,nuxt项目中如何进行图片懒加载 处理

这里首先声明一下,市面上其实有很多懒加载插件,但是不知道是不是因为nuxt3 和 lazy相关插件版本有兼容的问题,一致配置的不太好使

因此这个懒加载是我自己写的一个 自定义指令(本来也不复杂,所以干脆自己写了)

懒加载指令

在 plugins文件夹下,增加index.ts 文件,申明 v-Mlazy 指令

// plugins/index.js// 该指令 arg  用来 区分  img src  还是  标签  background import{useIntersectionObserver}from"@vueuse/core";exportdefaultdefineNuxtPlugin((nuxtApp)=>{// 不再是 import Vue from 'vue'的写法了
  nuxtApp.vueApp.directive('Mlazy',{// 不用mounted,在mounted时用户权限集还是空的mounted(el,{
      arg, 
      value
    }){if(typeof value !='string'){return}elseif(arg =='show'){
        el.style.background =`url(${value}) no-repeat   center`
        el.style.backgroundSize =`cover`return}elseif(arg =='imgShow'){
        el.src = value
        return}const{ stop }=useIntersectionObserver(
        el,([{ isIntersecting }])=>{if(isIntersecting){if(arg =='img'){
              el.src = value
            }elseif(!arg){
              
              el.style.background =`url(${value}) no-repeat   center`
              el.style.backgroundSize =`cover`}// 当组件卸载时,停止监听stop();}})}})})

页面中引用

nuxt3.0后 assets 里面的资源已经不允许动态拼接引入。

懒加载的地址,本地需要放到根目录下的 public文件下,网络图片不受限制

<template><div class="home-wrap"><div >
      Nuxt3--{{$t(`HOME.b1.title`)}}</div><div >{{$t(`HOME.b1.txt`)}}</div><div>
      这张图是  img
      <img v-Mlazy:img="'/back.png'"/></div><div v-Mlazy="'/back.png'">
      这张图是  背景图
    </div></div></template><script setup>const nuxtApp =useNuxtApp();useHead({...nuxtApp.$setHead('home')})</script><style lang="scss" scoped>.home-wrap{
  min-height:calc(100vh - 200px);&>div{width: 1200px;margin:0 auto;
    min-height: 200px;display: flex;
    justify-content: center;
    align-items: center;
    font-size: 50px;position: relative;}
  img{height: 200px;width:100%;position: absolute;
    z-index:-1;}}</style>

演示效果

在这里插入图片描述

fonts 字体库

api请求

动画

文章正在努力完善中。。。。。

标签: nuxt3 vue3 seo

本文转载自: https://blog.csdn.net/qq_38998250/article/details/138316706
版权归原作者 拿回忆下酒 所有, 如有侵权,请联系我们删除。

“Vue3+Nuxt3 从0到1搭建官网项目,SEO搜索、中英文切换、图片懒加载”的评论:

还没有评论