# Nuxt 3 ## :sparkles: Install ```terminal npx nuxi@latest init project_name pnpm dlx nuxi@latest init project_name ``` ## :sparkles: Configuration #### App config ```javascript // app.config.ts export default defineAppConfig({ title: 'Hello Nuxt', theme: { dark: true, colors: { primary: '#ff0000' } } }) // script setup const appConfig = useAppConfig() const title = appConfig.title // Hello Nuxt ``` #### Runtime config ```javascript // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { // The private keys which are only available server-side API_KEY: process.env.API_KEY, // Keys within public are also exposed client-side public: { API_BASE_URL: 'oh-my-god/api' } } }) // script setup const runtimeConfig = useRuntimeConfig(); runtimeConfig.public.API_BASE_URL ``` #### App config vs Runtime config - **App config** is used for buildtime config and public information when **Runtime config** is used to expose env or sensitive infomation like private token - Env support only to **Runtime config** - Client-side can access **App config** and public **Runtime config**, but can't access private **Runtime config** - Server-side can access all #### Environment overrides ```javascript export default defineNuxtConfig({ $production: { routeRules: { '/**': { isr: true } } }, $development: { // } }) ``` #### Path alias ```javascript alias: { "@utils": "../utils", } ``` #### Enable experiment features ```javascript // nuxt.config.js export default defineNuxtConfig({ vue: { defineModel: true, propsDestructure: true } }) ``` ##### defineModel ```jsx <script setup> const modelValue = defineModel() console.log(modelValue.value) </script> <template> <input v-model="modelValue" /> </template> ``` ## :sparkles: Views #### Nitro plugin ```javascript // server/plugins/extend-html.ts export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook("render:html", (html, { event }) => { // This will be an object representation of the html template. console.log(html); html.head.push( `<meta name="description" content="My custom description" />` ); }); // You can also intercept the response here. nitroApp.hooks.hook("render:response", (response, { event }) => { console.log(response); }); }); ``` ## Assets #### Public Referencing an image file in the public/img/nuxt.png directory, available at the static URL /img/nuxt.png ```jsx <template> <img src="/img/nuxt.png" alt="Discover Nuxt 3" /> </template> ``` #### Assets Global style import ```javascript export default defineNuxtConfig({ vite: { css: { preprocessorOptions: { scss: { additionalData: '@use "@/assets/_colors.scss" as *;' } } } } }) ``` Local component style import ```sass <style lang="scss" scoped> @import "~/assets/test.scss"; @import "~/assets/css/test.css"; </style> ``` ## Styling #### Font ```css /* main.css */ @font-face { font-family: 'Minecraft'; src: url('~/fonts/Minecrafter.Alt.ttf') format('truetype'); font-weight: normal; font-style: normal; font-display: swap; } ``` #### Postcss ```javascript // nuxt.config.ts export default defineNuxtConfig({ postcss: { plugins: { 'postcss-autoprefixer': {} } } }) ``` ```javascript // plugins/postcss-autoprefixer.js module.exports = { plugins: { autoprefixer: {} } } ``` ## Routing #### Middleware - kebab-case - to make it global, use suffix .global: auth.global.ts ```javascript // middleware/auth.global.ts export default defineNuxtRouteMiddleware(() => { console.log('global middleware') }) ``` ```javascript // pages/test.vue <script setup lang="ts"> definePageMeta({ middleware: "local", }); </script> ``` #### Route validation - if validate false, redirect to 404 page ```javascript definePageMeta({ validate: async (route) => { // Check if the id is made up of digits return /^\d+$/.test('a'); }, }); ``` ## Fetching Data #### $fetch - Built-in library ofetch - Can use anywhere - Using $fetch in components without wrapping it with useAsyncData causes fetching the data twice: initially on the server, then again on the client-side during hydration, because $fetch does not transfer state from the server to the client #### useFetch - Composable - Avoid refetching the same data when the code is executed in the browser - Shorthand of useAsyncData with $fetch #### useAsyncData - Composable - Avoid refetching the same data when the code is executed in the browser - More options than useFetch ```jsx <script setup lang="ts"> // During SSR data is fetched twice, once on the server and once on the client. const dataTwice = await $fetch('/api/item') // During SSR data is fetched only on the server side and transferred to the client. const { data } = await useAsyncData('item', () => $fetch('/api/item')) // You can also useFetch as shortcut of useAsyncData + $fetch const { data } = await useFetch('/api/item') </script> ``` ## State management - useState is an SSR-friendly ref replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key - To globally invalidate cached state, use clearNuxtState(id?) ```jsx <script setup lang="ts"> const counter = useState('counter', () => Math.round(Math.random() * 1000)) // const counter = useCounter() </script> <template> <div> Counter: {{ counter }} <button @click="counter++"> + </button> <button @click="counter--"> - </button> </div> </template> ``` ## Error handling #### During rendering lifecycle - You can hook into Vue errors using onErrorCaptured. Or global propagate up by using vue:error - For using an error reporting framework, you can provide a global handler through vueApp.config.errorHandler ```javascript export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.config.errorHandler = (error, context) => { // ... } }) ``` ## Key concept #### Auto import - Components ```javascript // change directory for auto import export default defineNuxtConfig({ components: { dirs: [] } }) ``` - Composables and Utils ```javascript // disable autoImport export default defineNuxtConfig({ imports: { autoImport: false } }) ``` - Built-in: fetch, useAsyncData (nuxt), ref, computed (Vue) - Cannot use them outside a Nuxt plugin, Nuxt route middleware or Vue setup function ```javascript const config = useRuntimeConfig() export const useMyComposable = () => { // accessing runtime config here => error } ``` ```javascript export const useMyComposable = () => { // Because your composable is called in the right place in the lifecycle => work const config = useRuntimeConfig() } ``` - Third-party package ```javascript export default defineNuxtConfig({ imports: { presets: [ { from: 'vue-i18n', imports: ['useI18n'] } ] } }) ``` #### Components - Custom auto import components ```javascript export default defineNuxtConfig({ components: [ { path: '~/user-module/components', options }, // path: '~/components', + extensions: ['.vue'], pathPrefix: false, prefix: false, ] }) ``` - Dynamic component ```jsx <script setup lang="ts"> import { SomeComponent } from '#components' // or const MyButton = resolveComponent('MyButton') </script> <template> <component :is="clickable ? MyButton : 'div'" /> </template> ``` - Lazy-loading component ```jsx <template> <div> <h1>Mountains</h1> <LazyMountainsList v-if="show" /> // component: MountainsList <button v-if="!show" @click="show = true">Show List</button> </div> </template> ``` - Client-only component ```jsx <template> <div> <Sidebar /> <!-- This renders the "span" element on the server side --> <ClientOnly fallbackTag="span"> <!-- this component will only be rendered on client side --> <Comments /> <template #fallback> <!-- this will be rendered on server side --> <p>Loading comments...</p> </template> </ClientOnly> </div> </template> ``` Or use suffix component name: Comments.client.vue - Server-only component, Dev-only component