Understand the basic usage of Nuxt3 in one article

Why choose Nuxt.js?

Before the separation of front and back ends, traditional web pages were rendered on the server side, such as JSP, PHP, Python Django, and various template technologies Freemarker, velocity, thymeleaf, mustache, etc. In fact, this set of technologies is quite mature and has been used for many years.

However, after the separation of front and back ends, it brings two benefits:

  • The division of labor in engineering allows the front-end to focus on the front-end technology, and the development efficiency is improved
  • Through various CDN and nodejs technologies, the performance of the front end can be continuously optimized, and the deployment method is more flexible and changeable, bringing more room for imagination

Which is better, Nuxt.js of Vue or Next.js of React? I don't have an answer yet, it seems the latter is more mature, but Nuxt is also developing rapidly.
Also, Ali's UmiJs are on the rise...

The key to understanding the working mode of Nuxt3: rendering mechanism

One of the biggest confusions when writing Nuxt3 code is: is the code you wrote running on the client or the server? What stage is it running in? . Because Nuxt3's rendering mechanism blurs the boundary between client and server. Also, some pages are generated during the build phase .

Note: Nuxt3 enables ssr by default (you can turn it off in nuxt.config.js), and it uses Universal rendering by default.

After ssr is turned on, even if the code in your /pages directory, such as axios request or $fetch request, Nuxt3 will execute it on the server side and retrieve data on the server side. If you call the client API such as sessionStorage in the request, an error will be reported. Be very careful about this.

Rendering mechanism supported by Nuxt3

Nuxt3 provides several different on-demand rendering mechanisms:

  • CSR: Client Side Rendering: Client side rendering only (CSR): The page is dynamically generated by JS in the browser: js fetches data from the background through ajax, and dynamically generates DOM

  • SSR: server-side rendering (SSR): The page is generated by nodejs on the server side: nodejs parses the Vue code, generates html at one time and returns it to the browser. The bonus that comes with this is search engine optimization (SEO).

  • SSG: static site generation
    insert image description here

  • ISR: Incremental Static Regeneration, not supported by vue, but supported by nuxt3, which needs to be provided by node
    insert image description here

  • ESR: Edge Side Rendering, the core idea is to return static content and dynamic content to users successively in a streaming manner with the help of edge computing capabilities. Compared with the server, the CDN node is closer to the user and has a shorter network delay. On the CDN node, the cacheable static part of the page is quickly returned to the user first, and at the same time, a dynamic part content request is initiated on the CDN node, and the generated static part is cached and then returned to the user.
    insert image description here

  • SWR: stale-while-revalidate, an HTTP cache invalidation strategy popularized by HTTP RFC 5861(opens in a new tab). This strategy first returns data from the cache (expired), at the same time sends a fetch request (revalidation), and finally gets the latest data. With SWR, components will constantly and automatically get the latest stream of data. The UI will always remain responsive as well. See: https://swr.vercel.app/zh-CN

  • Mixed rendering: different routes, different pages, using different rendering mechanisms. Hybrid Rendering mechanism:

Hybrid rendering allows different caching rules per route using Route Rules and decides how the server should respond to a new request on a given URL.

Therefore, we can configure different rendering strategies for different routes in nuxt config, including: redirect, ssr, cors, headers, static and swr options.

Nuxt3 provides HMR (Hot Module Replacement) hot update mechanism.

Nuxt's rendering process

insert image description here

In addition, other rendering mechanisms include NSR (Native Side Rendering), which first loads offline page templates in the list page, preloads page data through Ajax, generates Html data through Native rendering and caches it on the client.

Key Concepts: Modules, Middleware, and Plugins

  • module: The module provides a better extension and reuse mechanism. The module can provide vue components, combination API, and plug-ins to the outside world. Modules are defined via defineNuxtModule().
  • layer: a software package with nuxt.config and app.config configurations and a standard nuxt directory structure, easy to be reused by other projects: in nuxt.config.ts, a layer can be referenced by extending a layer
  • middleware: Intercept routing switching, each switching will call the logic of the middleware
  • plugin: Plug-ins can easily introduce third-party functional components, then hang them into nuxtApp and use them as global objects

So: Public and independent functions can be encapsulated into Composable or Plugin .

export default defineNuxtPlugin((nuxtApp) => {
    
    
  const instance = ofetch.create({
    
    
    baseURL: '/api',
    headers: {
    
    
      Accept: 'application/json'
    }
  })

  // You can also just do this instead of returning
  // nuxtApp.provide('ofetch', instance)

  return {
    
    
    provide: {
    
    
      ofetch: instance
    }
  }
})

Then, it can be used like this:

const {
    
     $ofetch } = useNuxtApp()

routing middleware

Routing middleware can do some things when the front-end route is switched. Divided into page-level and global two. Global middleware files end in .global.ts.
Middleware can be defined in plugins or pages, or it can be placed in the middleware directory as a separate file.
Define a middleware in the plugin:

export default defineNuxtPlugin(() => {
    
    
  addRouteMiddleware('global-test', () => {
    
    
    console.log('this global middleware was added in a plugin')
  }, {
    
     global: true })

  addRouteMiddleware('named-test', () => {
    
    
    console.log('this named middleware was added in a plugin')
  })
})

Define middleware (inline middleware) in the page:

<template>
  <div>
    Forbidden
  </div>
</template>

<script setup>
definePageMeta({
    
    
  // This is an example of inline middleware
  middleware: () => {
    
    
    console.log('Strictly forbidden.')
    return false
  }
})
</script>

Use middleware in the page:

<script setup>
definePageMeta({
    
    
  middleware: 'redirect-me'
})
</script>

The redirect-me middleware is called when routing to this page from other pages.
A page can be returned in routing middleware, for example:

export default defineNuxtRouteMiddleware((to, from) => {
    
        
    return 'test/main'
 })

Then the page to which the middleware is applied will jump directly to the pages/test/main.vue page. If you do not write a return statement, you will enter this page.

Nuxt3 project structure

package.json reference

{
    
    
  "name": "mall",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    
    
    "dev": "nuxt",
    "build": "nuxt build",
    "serve": "nuxt dev",
    "preview": "nuxt preview",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
  "dependencies": {
    
    
    "@nuxt/content": "^1.0.0",
    "@nuxtjs/pwa": "^3.3.5",
    "core-js": "^3.25.3",
    "element-plus": "^2.2.27",
    "@element-plus/icons-vue": "^2.0.10",
    "vue": "3.2.45",
    "pinia": "^2.0.14",
    "@pinia/nuxt": "^0.4.5"
  },
  "devDependencies": {
    
    
    "nuxt": "^3.0.0",
    "nuxt-windicss": "^2.5.5",
    "vite": "^3.2.4",
    "@nuxt/types": "^2.15.8",
    "@nuxt/typescript-build": "^2.1.0",
    "@iconify/vue": "^3.2.1",    
    "@vueuse/nuxt": "^8.4.2",
    "@windicss/plugin-animations": "^1.0.9",
    "sass": "^1.51.0"
  }
}

Directory Structure

The first point: to distinguish which is the client code, which is the server code.

Our code under the server can be understood as running on the server side. These APIs act as proxy forwarding to solve our front-end cross-domain problems, just like proxypass in nginx forwarding to the backend. Nuxt 3 internally uses Nitro server as the server, and Nitro uses the unjs/h3 framework to process requests and routing internally.

Conventional directory structure:
insert image description here

├── app.vue # Main component entry component in Nuxt 3 application
├── components # Component directory, support automatic import
├── layouts # Layout directory
├── composables # Public function, support automatic import
├── assets # The static resource directory is the same as the assets of the vue project
├── middleware # Routing middleware
├── nuxt.config.ts # Nuxt configuration file, which can be understood as vue.config.js The file name must be nuxt.config The suffix name can be .js, .ts or .mjs
├── package.json
├── pages # file-based routing
├── plugins #plug-in
├── public # will not participate in packaging, similar to the public of the vue project, directly linked to the server Root directory
├── README.md
├── server
├── tsconfig.json
└── yarn.lock

Notice:

  • If there is a nested directory under composables, you need to place index.ts in the nested directory, and then export the corresponding object in it.
  • The components component is nested in the directory and can be imported in camel case, such as importing components/user/avatar.vue, you can use <UserAvatar>
  • In the server directory, there can be subdirectories such as api, middleware, and plugin. Each file under api corresponds to a restful API. It seems that there is no way to define multiple APIs in one file.

import automatically

Nuxt3 supports automatic import (auto-import), which means that objects in composables, components and other directories can be used directly in vue components.

On the server side, the objects in the ~server/utils directory can also be automatically imported by the server-side code.

relationship between pages

The entry point is in app.vue:

<script setup>
import {
    
     ID_INJECTION_KEY } from "element-plus";
provide(ID_INJECTION_KEY, {
    
    
  prefix: 100,
  current: 0,
});
</script>
<template>
  <div>
    <NuxtLayout>
      <NuxtLoadingIndicator :height="5" :duration="3000" :throttle="400" />
      <NuxtPage />
    </NuxtLayout>
  </div>
</template>

Find a layout under layout through <NuxtPage>, you can specify the layout by name, the default is layouts/default.vue:

<!--default.vue文件-->
<template>
  <main class="py-2 px-10 text-center">
    <slot />
    <Footer />
    <div class="mt-5 mx-auto text-center opacity-25 text-sm">
      
    </div>
  </main>
</template>

The layout can also be specified in the page through the definePageMeta() macro.
Then, the slot in default.vue will be replaced by a page specified in the route, and the default page is pages/index.vue.
In the page, we can call each component we store in the components directory.

<template>
  <div>
    <Header />
    <PageWrapper>
      hello world
    </PageWrapper>
  </div>
</template>
<script lang="ts" setup>
import {
    
     ref } from "vue";
const activeIndex = ref('1')
const activeIndex2 = ref('1')
const handleSelect = (key: string, keyPath: string[]) => {
    
    
  console.log(key, keyPath)
}
const value1 = ref();
</script>
<style></style>

The above page uses two components: Header and PageWrapper, which correspond to the Header/index.vue and page/Wrapper.vue files under components respectively.
insert image description here
This is the calling relationship from app.vue->layout->page->component.

Page jump and parameter passing

page jump

  • Can use <NuxtLink>
  • Call the navigateTo() function
<script setup>
const router = useRouter();
const name = ref('');
const type = ref(1);
function navigate(){
    
    
  return navigateTo({
    
    
    path: '/search',
    query: {
    
    
      name: name.value,
      type: type.value
    }
  })
}
</script>
  • useRouter.push() function
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
function test() {
	router.back();
	router.forward();
	router.go();
	router.push({ path: "/home" });
	router.replace({ hash: "#bio" });
}
</script>

Note : const router = useRouter() needs to be placed in the setup() function. It cannot be directly put into the method, otherwise the router will be empty.

Dynamic routing parameters

  • The page is requested through the dynamic route /user/[id]/info, and the parameter id can be obtained through route.params.id on this page.
  • Through navigateTo() or router.push() routing, the corresponding parameters can be obtained through route.query.name and route.query.type in the page.

state sharing and persistence

Use useState() to share state between pages

Nuxt3 provides the useState composite function, which can be used to create a state that can be shared throughout the component. This state is also responsive and very friendly to SSR.
The reason why it is SSR-friendly is that if useState is used to save the state on the server side, this state will be serialized and sent to the client after rendering on the server side, so that the shared state can be used in all components of the client.

Note that useState can only be used in setup and lifecycle Hooks.

page1.view:

<template>
    {
    
    {
    
     counter }}
    <el-button @click="counter++">1</el-button>
    <el-button @click="counter--">1</el-button>
</template>

<script setup lang="ts">
const counter = useState("counter", () => Math.round(Math.random() * 1000)) // 定义并初始化
</script>

page2.view:

<template>
    {
    
    {
    
     counter }}
</template>

<script setup lang="ts">
const counter = useState("counter") // 引用已存在的对象
</script>

For example, we can share the currently logged-in user information between multiple pages.

Shared state and persistence with Pinia

yarn add -D @pinia-plugin-persistedstate/nuxt

Then, configure nuxt.config.ts:

export default defineNuxtConfig({
    
    
  modules: [
    '@pinia/nuxt',
    '@pinia-plugin-persistedstate/nuxt',
  ],
})

And then:

import {
    
     defineStore } from 'pinia'

export const useStore = defineStore('main', {
    
    
  state: () => {
    
    
    return {
    
    
      someState: 'hello pinia',
    }
  },
  persist: true,
})

Persistence is optional

persist: {
    
    
    storage: persistedState.localStorage,
  },

or

  persist: {
    
    
    storage: persistedState.sessionStorage,
  },

several main commands

  • nuxt starts a hot reloaded web server (development mode) localhost:3000.
  • nuxt build uses webpack to compile the app, minifying JS and CSS resources (for release).
  • nuxt start starts a web server in production mode (needs to run nuxt build first).
  • Nuxt generate compiles the application and generates the corresponding HTML file (for static site deployment) according to the routing configuration.

Main parameters:
–config-file or -c: Specify the file path of nuxt.config.js.
–spa or -s: disable server-side rendering, use SPA mode
–unix-socket or -n: specify the path of UNIX Socket.

dynamic component

To write dynamic components in vue, use the resolveComponent syntax:

<template>
  <div>
    <component :is="isHeader ? TheHeader : 'div'" />
  </div>
</template>
<script setup>
const isHeader = ref(1)
// 组件
const TheHeader = resolveComponent('TheHeader')
</script>

Background interaction: $fetch() and useFetch()

Nuxt3 no longer needs the axios module, and directly uses the built-in useFetch, useLazyFetch, useAsyncData and useLazyAsyncData methods. We usually use these APIs in modules under server/api.

$fetch is Nuxt3's encapsulation of ofetch. During server-side rendering, calling $fetch to obtain internal API routes will directly call related functions (simulating requests), saving additional API calls. Note that $fetch is the preferred way of making HTTP requests in Nuxt 3, rather than @nuxt/http and @nuxtjs/axios for Nuxt 2.

useFetch() encapsulates useAsyncData and $fetch, it will automatically generate key according to URL and fetch options, and infer API response type. By default, useFetch blocks navigation until its async handler is resolved.

const {
    
     users } = await $fetch('/api/users', {
    
     method: 'POST', body: {
    
     some: 'json' } })
// Adding baseURL
await $fetch('/config', {
    
     baseURL })

// Adding params
await $fetch('/movie?lang=en', {
    
     params: {
    
     id: 123 } })

await useFetch(() => "/my/post/url", {
    
    
        method: 'POST',
        mode: 'cors', // 允许跨域
        body: {
    
     some: true },
        initialCache: false,
        onResponse({
     
      request, response, options }) {
    
    
    	    // Process the response data	    	 
    	},
    	
    });

It can also be written like this: (This way of writing is not recommended, do not send requests directly to the backend in the vue component, but transfer them through the API)

onMounted(async () => {
    
    
    const{
    
     data, pending, error, refresh } = await useFetch(() => 'http://localhost:8888/cms/api/ebook/listall', {
    
    mode: 'cors'}, {
    
     immediate: true })
    const bookList = JSON.parse(data.value) // 注意data是一个vue的ref对象,需要.value获得其值
    bookList.forEach(book => {
    
    
      console.log(book.bookName)
    });
})

In the server/api program defineEventHandler(), you can directly return JSON data, a Promise or use event.res.end() to send a response.

The second parameter options of $fetch() is of type FetchOptions

interface FetchOptions<R extends ResponseType = ResponseType> extends Omit<RequestInit, "body"> {
    
    
    baseURL?: string;
    body?: RequestInit["body"] | Record<string, any>;
    params?: SearchParameters;
    query?: SearchParameters;
    parseResponse?: (responseText: string) => any;
    responseType?: R;
    response?: boolean;
    retry?: number | false;
    onRequest?(context: FetchContext): Promise<void> | void;
    onRequestError?(context: FetchContext & {
    
    
        error: Error;
    }): Promise<void> | void;
    onResponse?(context: FetchContext & {
    
    
        response: FetchResponse<R>;
    }): Promise<void> | void;
    onResponseError?(context: FetchContext & {
    
    
        response: FetchResponse<R>;
    }): Promise<void> | void;
}

The passed parameters are set by body, or params or query.

The difference between useFetch and useAsyncData

  • useFetch takes a URL and fetches that data, while useAsyncData may have more complex logic. useFetch(url) is almost equivalent to useAsyncData(url, () => $fetch(url)). useFetch is the package of useAsyncData
  • useAsyncData is developer experience sugar for the most common use cases. useAsyncData, do some simple get data requests, useFetch can do more complex post, put, delete and other requests
<script>
await useAsyncData(() => $fetch(`/api/hello/${
      
      ctr.value}`), {
    
     watch: [ctr] })
</script>

The most important point is : the operation in useAsyncData() is executed on the server side. If you want SSR, please put the operation of axios or fetch request data in useAsyncData().

The difference between useAsyncData and useLazyAsyncData

  • useLazyAsyncData is lazy: true wrapper of useAsyncData.
  • useLazyAsyncData is an asynchronous function that will not block navigation, but its initial value is null when it is pending, and it cannot be accessed immediately at the beginning, and the data can be obtained through watch monitoring

The use of useFetch in onMounted()

UseFetch() is used in onMounted, and it can be used only after a delayed call, otherwise the data cannot be obtained. The solution is to use the nextTick() method.
Data cannot be obtained by calling directly:

onMounted(() => {
    
        
 	const {
    
     data } = await useFetch('/api/ebook/getall')
    bookList.value = data.value
})

need:

onMounted(() => {
    
        
  nextTick(async () => {
    
    
    const {
    
     data } = await useFetch('/api/ebook/getall')
    bookList.value = data.value
  })
})

When the mounted hook function is executed, all DOM mounting and rendering have been completed. If an operation needs to be performed after the data changes, and this operation needs to change the DOM structure, this operation should be put into the callback function of Vue.nextTick() middle.

use axios

You can also continue to use the axios library under Nuxt3. The integration method is relatively simple. Write a plug-in and expose an axios instance:

import axios from "axios";
export default defineNuxtPlugin((nuxtApp) => {
    
    
  const defaultUrl = "<https://localhost:5001>";

  let api = axios.create({
    
    
    baseUrl: defaultUrl,
    headers: {
    
    
      common: {
    
    },
    },
  });
return {
    
    
    provide: {
    
    
      api: api,
    },
  };
});

Then you can call the method of this$.api.

Server Engine Nitro

The basis of Nitro is rollup and h3: a minimal http framework built for high performance and portability.

The new server engine Nitro Engine in nuxt3, the server core in nuxt2 uses connect.js, and nuxt3 uses the h3 framework developed by the nuxt team, which is characterized by strong portability and very lightweight level, and also supports middleware written by connect. That is to say, the server side written by nuxt3 based on h3 can be seamlessly transplanted to places that support the js operating environment.

The Nuxt3 development team has been working on Nitro, Nuxt's new server engine, for 9 months. It unlocks new full-stack capabilities for Nuxt servers and more.

In development, it uses rollup and Node.js workers to serve server-side code and context isolation. And generate your server-side API by reading the files in the server/api/ directory and the server-side functions in the server/functions directory.

In production, it builds your app and server code into separate .output directories. The output is lightweight: the code is minified and all Node.js modules are removed. You can deploy this product under any system that supports JavaScript, Node.js, Severless, Workers, Edge Side Rendering or pure static deployment.

This artifact includes runtime code to support running Nuxt servers in any environment (including experimental browser Service Workers!), and to start serving static files, making it a true JAMStack-compliant architecture. hybrid framework. It also implements a native storage layer that supports multiple origins, drivers, and local resources.

How to write on the server side

If your page needs SEO, you have to go through the server layer to fetch data. You can't directly use ajax in the page to find the data in the background. Instead, forwarding at the server layer is required.

Nuxt automatically scans files in ~/server/api, ~/server/routes, and ~/server/middleware directories to register API and server handlers with HMR support.
Each file should export a default function defined with defineEventHandler(). Handlers can return JSON data directly, a Promise or send a response using event.node.res.end().

context object nuxtApp

useNuxtApp() returns a nuxtApp instance mainly to provide a shared runtime context that can access nuxt, and this context exists on both the server and the client. Context includes: vue app instance, runtime hooks, runtime configuration variables and internal state, such as ssrContext and payload.

const nuxtApp = useNuxtApp()

nuxtApp is a runtime context that you can extend with plugins. Use the provide method to create a nuxt plug-in, specify the name, and call the value specified object through the name in all combined APIs and components.

const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${
      
      name}!`)

// Prints "Hello name!"
console.log(nuxtApp.$hello('name'))

For example, we define this in the plugin:

export default defineNuxtPlugin(async (nuxtApp) => {
    
    
	return {
    
    
	    provide: {
    
    
	      auth: {
    
    
	        loggedIn,
	        session,
	        redirectTo,
	        updateSession,
	      },
	    },
  };
})

Then in the client code, you can use it like this:

export const useAuth = () => useNuxtApp().$auth
userAuth().loggedIn()

This is a common technique for extending Nuxt applications.

A few points of attention

Remember clientOnly

Use the client-only tag to tell nuxt that no server-side rendering is required.

<template>
    <client-only>
        <vue-pdf-app style="height: 100vh;" :pdf="pdfUrl"></vue-pdf-app>
    </client-only>    
</template>

Debug Nuxt3

launch.json settings in vscode:

{
    
    
       "name": "serve",
       "type": "node",
       "request": "launch",
       "cwd": "${workspaceFolder}",
       "runtimeExecutable": "yarn",
       "runtimeArgs": ["serve"]
}

insert image description here

SSR deployment

Deploying Nuxt.js server-side rendering applications cannot directly use the nuxt command, but should be compiled and built first, and then the Nuxt service can be started, which can be completed by the following two commands:

nuxt build
nuxt start

Usually the script will be packaged as: yarn build or pnpm build command
to generate the .output folder after the build. This folder is the deployment file. The .output folder contains two directories, public and server. After renaming it to release, create an ecosystem.config.js file.

insert image description here
ecosystem.config.js file content:

module.exports = {
    
    
    apps: [
        {
    
    
            name: 'CMSFront',
            exec_mode: 'cluster',
            instances: 'max',
            script: './release/server/index.mjs',
            env: {
    
    
                NITRO_PORT: '9999',
            }
        }
    ]
}

Then start the node process with pm2:

pm2 start ecosystem.config.js

Of course, you must first install pm2:

npm install pm2 -g

Examples to understand SSR

The vue component is defined as follows:

<template>   
    <el-row :gutter="12" align="middle">     
        <el-col :span="4" v-for="book in bookList" style="margin-top:12px">
          <el-card class="box-card" style="height:380px;background-color: antiquewhite;" shadow="hover">
            <img :src="getCoverSrc(book.bookId)" class="image" />
            <span style="padding-bottom: 20px;">             
              {
    
    {
    
     book.bookName }}
            </span>
            
            <div class="card_footer">
              价格:9.9</div>
          </el-card>          
        </el-col>   
  </el-row>  
</template>

<script lang="ts" setup>
import {
    
     ref } from 'vue';
const bookList:[] = ref()
const currentDate = ref(new Date())

function getCoverSrc(bookId) {
    
    
  return `http://localhost:8888/cms/api/ebook/cover/${
      
      bookId}`
}


onMounted(async () => {
    
        
  const {
    
     data } = await useFetch('/api/ebook/getall')
  bookList.value = data.value
})
</script>

This component displays a list of books. The server/api called is as follows:

import {
    
     $fetch } from 'ofetch'

export default defineEventHandler(async (event) => {
    
    
  const books = await $fetch('http://localhost:8888/cms/api/ebook/listall')
  return books
})

This API fetches data from the backend.
If SSR rendering is not used, the page returned to the front end does not contain book list data, and the front-end js will request the book list through ajax. After adopting SSR, obtaining the book list is completed on the server side, and what is returned to the front end is the rendered html fragment of the book list.
Request the page through the curl command (each page has its own URL), and you can verify it.

Packing method

nuxi build
will generate .nuxt files for us

deploy

Three deployment forms:

  1. SSR rendering deployment. First nuxi build, then nuxi start
  2. Static deployment. First, compile nuxi generate into a static file, and a dist folder will be generated, in which all static resource files are located. Then throw it on nginx
  3. SPA deployment. nuxi build --spa, automatically generate the dist/ folder, and then throw it on nginx

UI framework

UI frameworks are usually divided into CSS frameworks and UI component frameworks. The former includes tailwindcss, windicss, unocss, and the latter includes niave UI, element plus, etc. CSS frameworks are higher-level than native CSS standards, and are easier to remember and write.

css: tailwind和windicss

Tailwind/windicss adopts a customary style, which can simplify and semanticize the writing of our class. It is somewhat similar to bootstrap, and element-plus, antd are complementary and overlap.
Install:

yarn add nuxt-windicss -D

Then, in nuxt.config.ts:

modules: [
   'nuxt-windicss'   
]

There is also a CSS framework unocss, you can refer to it.

UI components

Using Elment Plus

Install:

yarn add element-plus
yarn add @element-plus/nuxt -D

Configuration:

export default defineNuxtConfig({
    
    
  modules: [
    '@element-plus/nuxt'
  ],
  elementPlus: {
    
     /** Options */ }
})

Then all Element Plus components can also be imported directly and automatically, except for icons, which need to be imported manually:

<script lang="ts" setup>
import {
    
     Document } from '@element-plus/icons-vue'
</script>

Naive UI

https://www.naiveui.com/zh-CN/os-theme

Foreigner's recommendation:

  • tailwind
  • Element Plus: I was really hoping I would like this but I didn’t really enjoy it. Very stable, large community. 8/10
  • NaiveUI: I really like this one, smaller community than some of the others. I am leaning toward using it though. 7/10
  • Ant Design: Seems quite stable, not my style so I didn’t explore too far. 7/10
  • Vuestic: Smaller than some of the others and missing some key components. Looks promising but I am not comfortable using it for a new production project today. 6/10
  • Equal: Small and lightweight. organized well. 6/10
  • Headless UI. Super small but it makes using tailwind a more viable option. 6/10
  • PrimeVue: I have spent the most time with this one. It seems pretty good and is somewhat like Vuetify. There are several paid upgrades, which is fine with me, but something to note for others. 7/10
  • daisyui, https://daisyui.com
  • headlessui,https://headlessui.com
  • Vuetify

reference documents

Related Toolchain

  • Vite、Rollup、Nuxt、Webpack、Vue Cli、Quasar、Esbuild
  • Import components on demand: unplugin-vue-components realizes fully automatic on-demand mapping into the UI component library, and unplugin-auto-import realizes fully automatic import of function libraries

Guess you like

Origin blog.csdn.net/jgku/article/details/128605460