Nuxt3をさわるメモ
超久しぶりにNuxtをさわるのでメモを残す。ただしVue自体久しぶりなのでVue全般のことも含まれる。
ドキュメントを読みながら必要そうなところをまとめる。
Linting
{
"extends": [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"prettier"
]
}
eslint-plugin-vueがまだFlat Configに対応してなさそうなので今まで通りに(issue)。
Nuxtを使っているとpagesディレクトリでComponent name "index" should always be multi-word. eslint (vue/multi-word-component-names)
と怒られるけどどうしたらいいのだろうか。eslint-plugin-nuxtは一年前から更新がなさそうだし。
Auto-importを利用するとeslint/no-undef
で怒らるので、Nuxt ComposablesとNuxt Utils配下にある関数と、Vue API Referenceにある関数をglobals
に設定した。
あと、@typescript-eslintも導入した。
.eslintrc.json
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended",
"prettier"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"globals": {
"useAppConfig": "readonly",
"useAsyncData": "readonly",
"useCookie": "readonly",
"useError": "readonly",
"useFetch": "readonly",
"useHeadSafe": "readonly",
"useHead": "readonly",
"useHydration": "readonly",
"useLazyAsyncData": "readonly",
"useLazyFetch": "readonly",
"useLoadingIndicator": "readonly",
"useNuxtApp": "readonly",
"useNuxtData": "readonly",
"useRequestEvent": "readonly",
"useRequestHeader": "readonly",
"useRequestHeaders": "readonly",
"useRequestURL": "readonly",
"useRoute": "readonly",
"useRouter": "readonly",
"useRuntimeConfig": "readonly",
"useSeoMeta": "readonly",
"useServerSeoMeta": "readonly",
"useState": "readonly",
"$fetch": "readonly",
"abortNavigation": "readonly",
"addRouteMiddleware": "readonly",
"callOnce": "readonly",
"clearError": "readonly",
"clearNuxtData": "readonly",
"clearNuxtState": "readonly",
"createError": "readonly",
"defineNuxtComponent": "readonly",
"defineNuxtRouteMiddleware": "readonly",
"definePageMeta": "readonly",
"defineRouteRules": "readonly",
"navigateTo": "readonly",
"onBeforeRouteLeave": "readonly",
"onBeforeRouteUpdate": "readonly",
"onNuxtReady": "readonly",
"prefetchComponents": "readonly",
"preloadComponents": "readonly",
"preloadRouteComponents": "readonly",
"prerenderRoutes": "readonly",
"refreshNuxtData": "readonly",
"reloadNuxtApp": "readonly",
"setPageLayout": "readonly",
"setResponseStatus": "readonly",
"showError": "readonly",
"updateAppConfig": "readonly",
"nextTick": "readonly",
"defineComponent": "readonly",
"defineAsyncComponent": "readonly",
"defineCustomElement": "readonly",
"ref": "readonly",
"computed": "readonly",
"reactive": "readonly",
"readonly": "readonly",
"watchEffect": "readonly",
"watchPostEffect": "readonly",
"watchSyncEffect": "readonly",
"watch": "readonly",
"isRef": "readonly",
"unref": "readonly",
"toRef": "readonly",
"toValue": "readonly",
"toRefs": "readonly",
"isProxy": "readonly",
"isReactive": "readonly",
"isReadonly": "readonly",
"shallowRef": "readonly",
"triggerRef": "readonly",
"customRef": "readonly",
"shallowReactive": "readonly",
"shallowReadonly": "readonly",
"toRaw": "readonly",
"markRaw": "readonly",
"effectScope": "readonly",
"getCurrentScope": "readonly",
"onScopeDispose": "readonly",
"onMounted": "readonly",
"onUpdated": "readonly",
"onUnmounted": "readonly",
"onBeforeMount": "readonly",
"onBeforeUpdate": "readonly",
"onBeforeUnmount": "readonly",
"onErrorCaptured": "readonly",
"onRenderTracked": "readonly",
"onRenderTriggered": "readonly",
"onActivated": "readonly",
"onDeactivated": "readonly",
"onServerPrefetch": "readonly",
"provide": "readonly",
"inject": "readonly",
"hasInjectionContext": "readonly"
}
}
Assets
public/
ルートでそのまま提供される。/path/to/file
のように/
から参照する。
assets/
ビルドツール(Vite、Webpackなど)で処理するファイルを置く。~/assets/path/to/file
のように参照する。~
はルートへのエイリアス。
Styling
<script>
import "~/assets/css/main.css"
</script>
<style>
@import url("~/assets/css/main.css");
</style>
cssのインポートは<script>
と<style>
のどちらからでもできる。どちらもインラインに展開される。nuxt.config.ts
でもできるそう。
<style>
の方を使用しようと思う。
nuxt.config.ts
のapp.head
を使えlink要素を使ってcssを読み込める。これは静的に読み込む方法で、動的に読み込むにはコンポーネントでuseHead
を使う。
SFC
PostCSSはプラグインをnuxt.config.ts
で設定して、コンポーネントでは<style lang="postcss">
とする。
Scoped StylesとCSS Modulesの両方が使えるらしい。どっちがいいんだろうか。
Transitions
TODO
<!-- before -->
<style scoped>
@import url("the-new-css-reset");
@import url("~/assets/global.css");
</style>
// after
export default defineNuxtConfig({
css: ["the-new-css-reset", "~/assets/global.css"],
});
グローバルなcssをstyle要素で読み込もうとしたらScoped Stylesと相性が悪かったので、nuxt.config.ts
の方で読み込むことにした。
Routing
Pages
ファイル名がそのままパスになる。例えば~/pages/foo/bar.vue
は/foo/bar
になる。
ダイナミックルートは[]
を使う(users-[group]
、[id].vue
)。Optionalは二重括弧[[]]
を使う。
<script setup>
const route = useRoute();
console.log(route.params.group);
console.log(route.params.id);
</script>
<template>
<p>{{$route.params.group}} - {{$route.params.id}}</p>
</template>
値は<script setup>
ではuseRouter
を使い、テンプレート内では$route
から参照する。
Catch-allはファイル名を[...sulg]
のようにする。Optionalはなさそう。
Navigation
<NuxtLink to="path" />
リンクはNuxtLink
を使う。
/pages/article.vue
/pages/article/[id].vue
このようにルーティングを用意すると/article/...
で常にarticle.vue
の内容しか表示されない。
/pages/article/index.vue
/pages/article/[id].vue
このようにすると/article
ではindex.vue
が、/article/...
では[id].vue
が表示される。
なんでー?
SEO and Meta
全体の<head>
を変更するにはnuxt.config.ts
のapp.head
を使用する。
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
}
}
});
コンポーネント内で変更するにはuseHead
を使う。Unheadが使用されている。
ほかにもHead
コンポーネントが使えるらしいけど違いはなんだろうか。
Server
NuxtのサーバーはNitroが使われていて、Nitroではh3が使われている。なので、Nuxtで説明がないがNitroやh3のドキュメントで説明されていることがある。
export default defineEventHandler((event) => {
// return json
return { hello: "world" };
// or Promise
return $fetch("http://example.com");
// or event.node.res.end()
event.node.res.end("foo");
});
イベントハンドラーを引数に渡したdefineEventHandler
をデフォルトエクスポートする。ハンドラーはasync関数も大丈夫(多分)。
サーバーの処理は~/server
以下に書く。
Server Routes
~/server/api/article.ts -> /api/article
~/server/api/article/[id].ts -> /api/article/foo
~/server/api/article/[...slug].ts -> /api/article/foo/bar
~/server/api
に置かれたコードは/api
以下でアクセスできる。もし/api
が嫌なら~/server/route
に書くと/
以下から直接アクセスできる。[id]
や[...slug]
なども使える。
export default defineEventHandler((event) => {
const id = getRouterParam(event, "id");
const id = getRouterParams(event);
});
パラメターはgetRouterParam
などから使える。
export default defineEventHandler((event) => {
const id = parseInt(event.context.params.id) as number;
if (!Number.isInteger(id)) {
throw createError({
statusCode: 400,
statusMessage: "ID should be an integer",
});
};
return "All good";
});
エラーを返すにはcreateError
をスローする。