Nuxt: Difference between revisions
Created page with "=Whirlwind Tour= ==nuxt.config== Where your modules go e.g. eslint ==Hello World== All starts in App.Vue <syntaxhighlight lang="vue"> <template> <div> <div>Hello World</div> </div> </template> </syntaxhighlight> And to run it uses port 3000 <syntaxhighlight lang="bash"> npm run dev </syntaxhighlight> ==ESLint== This is how he did it in nuxt.config.ts <syntaxhighlight lang="tsc"> export default defineNuxtConfig({ modules: ['@nuxt/eslint'], devtools: { enabled:..." |
|||
(19 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=Whirlwind Tour= | =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 | Where your modules go e.g. eslint | ||
=Hello World= | |||
All starts in App.Vue | All starts in App.Vue | ||
<syntaxhighlight lang="vue"> | <syntaxhighlight lang="vue"> | ||
Line 15: | Line 17: | ||
npm run dev | npm run dev | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=ESLint= | |||
This is how he did it in nuxt.config.ts | This is how he did it in nuxt.config.ts | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="ts"> | ||
export default defineNuxtConfig({ | export default defineNuxtConfig({ | ||
modules: ['@nuxt/eslint'], | modules: ['@nuxt/eslint'], | ||
Line 33: | Line 35: | ||
}, | }, | ||
}) | }) | ||
</syntaxhighlight> | |||
=Pico CSS= | |||
He seemed to like this. I guess just another bunch on style names to remember. | |||
<syntaxhighlight lang="bash"> | |||
npm i @picocss/pico | |||
</syntaxhighlight> | |||
In the nuxt config you can say which css modules you want auto imported. So for pico you can add | |||
<syntaxhighlight lang="ts"> | |||
export default defineNuxtConfig({ | |||
modules: ['@nuxt/eslint'], | |||
devtools: { enabled: true }, | |||
css: ['@picocss/pico'] | |||
.... | |||
}) | |||
</syntaxhighlight> | |||
=Directories= | |||
Like nextjs the nuxt also using prescribe folder structure. This include, layouts, pages, api. There are lots and can be found [https://nuxt.com/docs/guide/directory-structure/nuxt here] | |||
==Layouts== | |||
As mentioned this is for layouts. So you can create a layouts/default.vue | |||
<syntaxhighlight lang="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> | |||
</syntaxhighlight> | |||
==Pages== | |||
In the app.vue we have this which tells it to use the layouts and page | |||
<syntaxhighlight lang="vue"> | |||
<template> | |||
<NuxtLayout> | |||
<NuxtPage /> | |||
</NuxtLayout> | |||
</template> | |||
</syntaxhighlight> | |||
Make a pages folder and create an index.vue, gosh wish they had #t instead of template. | |||
<syntaxhighlight lang="vue"> | |||
<template> | |||
<h1>Hello World</h1> | |||
</template> | |||
</syntaxhighlight> | |||
==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. | |||
<syntaxhighlight lang="vue"> | |||
<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> | |||
</syntaxhighlight> | |||
==Links== | |||
Gosh this is simple. So to do this you have two options. Name of page or route e.g. | |||
<syntaxhighlight lang="vue"> | |||
<template> | |||
... | |||
<ul> | |||
<li> | |||
<NuxtLink :to="{name: 'create'}" | |||
</li> | |||
<li> | |||
<NuxtLink to="/create" | |||
</li> | |||
</ul> | |||
</template> | |||
</syntaxhighlight> | |||
==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=== | |||
<syntaxhighlight lang="ts"> | |||
export default defineEventHandler(() => { | |||
return [ | |||
{ | |||
id: 1, | |||
title: "Task 1", | |||
done: false, | |||
}, | |||
{ | |||
id: 2, | |||
title: "Task 2", | |||
done: true, | |||
}, | |||
] | |||
}) | |||
</syntaxhighlight> | |||
Now we can use this in our index.vue by using the useFetch composeable. | |||
<syntaxhighlight lang="vue"> | |||
<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> | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="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> | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="ts"> | |||
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; | |||
}); | |||
</syntaxhighlight> | |||
===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. | |||
<syntaxhighlight lang="vue"> | |||
<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> | |||
</syntaxhighlight> | |||
=Fetch= | |||
==Which Fetch== | |||
There are three fetch options for nuxt. '''$fetch''', '''useFetch''' and '''useAsyncData'''. Here is a table to help taken from [https://masteringnuxt.com/blog/when-to-use-fetch-usefetch-or-useasyncdata-in-nuxt-a-comprehensive-guide here]<br> | |||
[[File:Nuxt fetching.png]] | |||
==Transform and Pick== | |||
You can use transform on a useFetch to transform the data. E.g. | |||
<syntaxhighlight lang="vue"> | |||
<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> | |||
</syntaxhighlight> | |||
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. | |||
<syntaxhighlight lang="ts"> | |||
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 | |||
} | |||
} | |||
) | |||
</syntaxhighlight> | </syntaxhighlight> |
Latest revision as of 06:10, 19 June 2025
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
}
}
)