Nuxt

From bibbleWiki
Jump to navigation Jump to search

Whirlwind Tour

This seems to be nitro + vue + something else I will remember. And I did H3 which is the base Web Framework.

nuxt.config

Where your modules go e.g. eslint

Hello World

All starts in App.Vue

<template>
  <div>
    <div>Hello World</div>
  </div>
</template>

And to run it uses port 3000

npm run dev

ESLint

This is how he did it in nuxt.config.ts

export default defineNuxtConfig({
  modules: ['@nuxt/eslint'],
  devtools: { enabled: true },
  compatibilityDate: '2025-05-15',
  eslint: {
    config: {
      stylistic: {
        semi: false,
        quotes: 'double',
        commaDangle: 'always-multiline',
        indent: 2,
      },
    },
  },
})

Pico CSS

He seemed to like this. I guess just another bunch on style names to remember.

npm i @picocss/pico

In the nuxt config you can say which css modules you want auto imported. So for pico you can add

export default defineNuxtConfig({
  modules: ['@nuxt/eslint'],
  devtools: { enabled: true },
  css: ['@picocss/pico']
....
})

Directories

Like nextjs the nuxt also using prescribe folder structure. This include, layouts, pages, api. There are lots and can be found here

Layouts

As mentioned this is for layouts. So you can create a layouts/default.vue

<template>
  <div class="container">
    <nav>
      <ul>
        <li><strong>Acme Corp</strong></li>
      </ul>
      <ul>
        <li><a href="#">About</a></li>
        <li><a href="#">Services</a></li>
        <li><a href="#">Products</a></li>
      </ul>
    </nav>
    <main class="container">
      <slot />
    </main>
  </div>
</template>

Pages

In the app.vue we have this which tells it to use the layouts and page

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

Make a pages folder and create an index.vue, gosh wish they had #t instead of template.

<template>
  <h1>Hello World</h1>
</template>

Components

This is for styles just kidding. If you make sub-directories the components get prefixed with the sub directory. E.g. components/app/navbar.vue will be named AppNavBar.

<template>
  <nav>
    <ul>
      <li><strong>Tasks App</strong></li>
    </ul>
    <ul>
      <li><a href="#">About</a></li>
      <li><a href="#">Services</a></li>
      <li><a href="#">Products</a></li>
    </ul>
  </nav>
</template>

Links

Gosh this is simple. So to do this you have two options. Name of page or route e.g.

<template>
...
    <ul>
      <li>
         <NuxtLink :to="{name: 'create'}"
      </li>
      <li>
         <NuxtLink to="/create"
      </li>
    </ul>
</template>

Server/API

In the api directory you create files with a method e.g. server/api/tasks.get.ts will is how you would make an endpoint for a tasks get request.

Getting Data

export default defineEventHandler(() => {
  return [
    {
      id: 1,
      title: "Task 1",
      done: false,
    },
    {
      id: 2,
      title: "Task 2",
      done: true,
    },
  ]
})

Now we can use this in our index.vue by using the useFetch composeable.

<script setup lang="ts">
const { data: tasks, error, status } = await useFetch("/api/tasks")
</script>

<template>
  <div>
    <article
      v-if="status==='pending'"
      aria-busy="true"
    />
    <article
      v-else-if="status==='error'"
      class="error"
    >
      <div>
        <div v-if="error?.statusMessage">
          {{ error.statusMessage }}
        </div>
        <div v-else>
          {{ error }}
        </div>
      </div>
    </article>
    <div v-else>
      <article
        v-for="task in tasks"
        :key="task.id"
      >
        {{ task.title }}
      </article>
    </div>
  </div>
</template>

Posting Data

So Nuxt uses ofetch which is a library wrapper for fetch. I don't think it needs any explanation. I guess the testing of status is done for you and automatically throwsp. The navigateTo is a Nuxt thing and relies on the vodoo of Nuxt like Next where you have to name things correctly or it breaks. In this case you folder in pages called tasks with a file called [id].vue.

<script lang="ts" setup>
import type { FetchError } from "ofetch";

const errorMessage = ref("");
const loading = ref(false);
const taskName = ref("");

async function onSubmit() {
	if (!taskName.value.trim()) {
		errorMessage.value = "Task is required.";
		return;
	}
	try {
		loading.value = true;
		errorMessage.value = "";
		const result = await $fetch("/api/tasks", {
			method: "POST",
			body: {
				title: taskName.value,
			},
		});
		navigateTo({
			name: "tasks-id",
			params: {
				id: result.id,
			},
		});
	}
	catch (e) {
		const error = e as FetchError;
		errorMessage.value = error.statusMessage || "Unknown error occurred";
	}
	loading.value = false;
}
</script>

Getting a Single Result

This is also easy enough. Once again the voodoo is used under server/api/tasks/[id].get.ts. I did like the built in getValidatedRouterParams which uses the familiar zod approach.

const IdParamsSchema = z.object({
	id: z.coerce.number(),
});

export default defineEventHandler(async (event) => {
	const result = await getValidatedRouterParams(event, IdParamsSchema.safeParse);

	if (!result.success) {
		return sendError(event, createError({
			statusCode: 422,
			statusMessage: "Invalid id",
		}));
	}

	const task = await db.query.tasks.findFirst({
		where: eq(tasks.id, result.data.id),
	});

	if (!task) {
		return sendError(event, createError({
			statusCode: 404,
			statusMessage: "Task not found",
		}));
	}

	return task;
});

Showing a Single Result

One the fronted we can show a result with this code. The main thing is how to use extract the id from the route. I must admit I do not like this approach where the id is in the route. Maybe this was for demo purposes.

<script lang="ts" setup>
const route = useRoute();
const { data: task, error, status } = await useFetch(`/api/tasks/${route.params.id}`, {
	lazy: true,
});
</script>

<template>
	<div>
		<article
			v-if="status === 'pending'"
			aria-busy="true"
		/>
		<article
			v-else-if="error"
			class="error"
		>
			{{ error.statusMessage }}
		</article>
		<div v-else-if="task">
			<article>
				{{ task.title }}
			</article>
		</div>
	</div>
</template>

Fetch

Which Fetch

There are three fetch options for nuxt. $fetch, useFetch and useAsyncData. Here is a table to help taken from here

Transform and Pick

You can use transform on a useFetch to transform the data. E.g.

<script>
const { pending, data: products} = useFetch(
  "https://fakestoreapi.com/products",
  {
    lazy: false,
    transform: (product) => {
        return products.map((product) => ({
            id: product.id, 
            title: product.title,
            image: product.image
        }))
    }
  }
)
</script>

It doesn't stop the initial request but does stop the client. Similarly you can use pick for a single quest.

useAsycData

Loving this, exactly like nextJS, well the way I use it.

const {
   pending,
   data: productInfo,
   refresh
} = useAsyncData(
  "productInfo",
   async () => {
       const [products, categories] = await Promise.all([
          $fetch("https://fakestoreapi.com/products"),
          $fetch("https://fakestoreapi.com/categories"),       
       ])
       return {
         products,
         categories 
       }
   }
)