Base project documentation — Nuxt 4 Template
| Category | Package | Version |
|---|---|---|
| Framework | Nuxt | 4.4.x |
| Language | TypeScript | 6.x |
| UI | Vue | 3.5.x |
| Font | YakuHanJP | 4.x |
| SEO - Sitemap | @nuxtjs/sitemap | 8.x |
| SEO - Robots | @nuxtjs/robots | 6.x |
| Linting | ESLint (@nuxt/eslint) | 10.x |
| Formatting | Prettier | 3.x |
app/
├── assets/css/global.css # CSS reset & base styles (YakuHanJP font)
├── components/ # Vue components
├── composables/
│ ├── useOptimizedSrc.ts # microCMS image optimize (resize, webp)
│ ├── useSeo.ts # SEO meta composable (title, desc, OG, canonical)
│ └── useJsonLd.ts # JSON-LD structured data composables
├── layouts/default.vue # Layout mặc định
├── pages/index.vue # Trang chủ
├── plugins/
│ └── gtm.client.ts # GTM delay load (window.load + 2s)
├── types/index.ts # Shared TypeScript types
├── utils/format.ts # Utility functions (date, string, url)
└── app.vue # Root component
server/
├── api/
│ ├── __sitemap__/urls.ts # Dynamic sitemap URLs
│ └── example.get.ts # API route mẫu
└── utils/
├── api-handler.ts # safeEventHandler (try/catch wrapper)
└── microcms.ts # microCMS client ($fetch, server-only)| Biến | Mô tả | Ví dụ |
|---|---|---|
SITE_URL | URL site cho sitemap & canonical | "https://example.com" |
TYPE_ROBOTS | Robots meta tag | "index, follow" |
MICRO_CMS_API_KEY | microCMS API key (server-only) | — |
MICRO_CMS_SERVICE_DOMAIN | microCMS service domain (server-only) | "your-service" |
GTM_ID | Google Tag Manager ID | "GTM-XXXXXXX" |
MICRO_CMS_* chỉ dùng ở server, không bị expose ra client. Biến public trong runtimeConfig.public expose ra cả client. Composables được Nuxt auto-import, dùng trực tiếp trong <script setup> không cần import.
Set title, description, canonical, Open Graph, Twitter Card, robots cho mỗi page. Mỗi page bắt buộc phải gọi.
useSeo({
title: 'ページタイトル | サイト名', // Bắt buộc
description: '説明文 120〜160文字...', // Bắt buộc, 120-160 ký tự
ogImage: '/images/og-page.webp', // Tối thiểu 1200x630px
// canonicalUrl: 'https://...', // Override nếu cần
// noindex: true, // Ẩn khỏi search engine
})| Option | Type | Bắt buộc | Mô tả |
|---|---|---|---|
title | string | Yes | Title page, format: ページ名 | サイト名 |
description | string | Yes | Meta description, 120-160 ký tự |
ogImage | string | OG image URL, tối thiểu 1200x630px, format WebP | |
canonicalUrl | string | Override canonical URL (mặc định tự sinh) | |
noindex | boolean | Set robots noindex, nofollow |
Tự động thêm query params resize, quality, format cho URL ảnh microCMS. URL khác trả nguyên, null/undefined trả /images/default.webp.
const { optimizeSrc } = useOptimizedSrc()
// Default: width=1200, quality=100, format=webp
optimizeSrc(article.thumbnail)
// Custom options
optimizeSrc(article.thumbnail, { width: 600, quality: 80 })Inject <script type="application/ld+json"> vào head.
| Loại page | Composable | Bắt buộc? |
|---|---|---|
| Homepage | useJsonLdOrganization() | Yes |
| Mọi page có breadcrumb | useJsonLdBreadcrumb() | Yes |
| Bài viết / Blog | useJsonLdArticle() | Yes |
| Tin tức | useJsonLdNewsArticle() | Yes |
| Trang FAQ | useJsonLdFaq() | Yes |
// Homepage
useJsonLdOrganization({
name: 'Site Name',
url: 'https://example.com',
logo: '/images/logo.png',
})
// Breadcrumb
useJsonLdBreadcrumb([
{ name: 'ホーム', url: 'https://example.com' },
{ name: 'ブログ', url: 'https://example.com/blog' },
{ name: article.title, url: `https://example.com/blog/${article.id}` },
])
// Article
useJsonLdArticle({
headline: article.title,
description: article.excerpt,
image: article.thumbnail,
datePublished: article.publishedAt,
dateModified: article.updatedAt,
author: { name: 'Author Name' },
publisher: { name: 'Site Name', logo: '/images/logo.png' },
})
// FAQ
useJsonLdFaq([
{ question: '質問1?', answer: '回答1' },
{ question: '質問2?', answer: '回答2' },
]) Tất cả hàm trong utils/ được Nuxt auto-import, dùng trực tiếp trong <template> và <script setup>.
Tất cả hàm date tự động convert UTC sang giờ Nhật Bản (Asia/Tokyo).
| Hàm | Output | Live demo |
|---|---|---|
formatDateDot(date) | YYYY.MM.DD | 2021.07.03 |
formatDateTimeDot(date) | YYYY.MM.DD hh:mm:ss | 2021.07.04 00:30:00 |
formatDateJP(date) | YYYY年M月D日 | 2021年7月3日 |
| Hàm | Mô tả | Live demo |
|---|---|---|
truncate(str, max) | Cắt chuỗi + ellipsis | truncate('hello world', 5) → hello... |
getDomain(url) | Lấy hostname từ URL | getDomain('https://example.com/path') → example.com |
GTM delay load: đợi window.load + 2 giây mới inject script → không ảnh hưởng Core Web Vitals.
GTM_ID = không load GTMnuxt.config.ts → runtimeConfig.public.GTM_ID: 'GTM-XXXXXXX'Kiến trúc: Client → Server Route → External API. API key nằm ở server, client không bao giờ thấy.
Wrapper cho defineEventHandler — bọc toàn bộ logic trong try/catch, server route không bao giờ crash.
// server/api/blogs.get.ts
export default safeEventHandler(async (event) => {
const { data, error } = await safeMicroCmsGet('/blogs')
if (error) {
throw createError({ statusCode: error.statusCode, message: error.message })
}
return data
}) Dùng $fetch (ofetch) built-in của Nuxt. Retry 2 lần, timeout 10s.
| Hàm | Mô tả | Throw? |
|---|---|---|
microCmsGet(endpoint, params?) | Gọi GET, trả data trực tiếp | Yes — cần try/catch |
safeMicroCmsGet(endpoint, params?) | Safe wrapper, trả { data, error } | Không bao giờ |
microCmsGetList(endpoint, params?) | Lấy list với phân trang | Yes — cần try/catch |
// Lấy list (safe)
const { data, error } = await safeMicroCmsGet('/blogs', {
limit: 10,
offset: 0,
})
// Lấy single content
const blog = await microCmsGet('/blogs/abc123')
// Gọi API khác (không phải microCMS)
const data = await $fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${useRuntimeConfig().someApiKey}` },
retry: 2,
})title: ngắn gọn, chứa keyword, format ページ名 | サイト名description: 120-160 ký tự, mô tả nội dung trang| Môi trường | TYPE_ROBOTS | Kết quả |
|---|---|---|
| Development / Staging | noindex, nofollow | Google KHÔNG index |
| Production | index, follow | Google index bình thường |
Tự động sinh từ SITE_URL + route.path. Override bằng canonicalUrl trong useSeo() khi cần.
@nuxtjs/sitemapserver/api/__sitemap__/urls.tshttps://your-site.com/sitemap.xml Mặc định: lang="ja". Thay đổi trong nuxt.config.ts → app.head.htmlAttrs.lang.
robots.txtsitemap.xml// SAI — API key bị lộ ra browser
const data = await $fetch('https://xxx.microcms.io/api/v1/blogs', {
headers: { 'X-MICROCMS-API-KEY': 'secret' },
})
// DUNG — Gọi qua server route
const { data } = await useFetch('/api/blogs')// SAI — nếu lỗi sẽ crash
export default defineEventHandler(async () => {
const data = await microCmsGet('/blogs')
return data
})
// DUNG — có safe wrapper
export default safeEventHandler(async () => {
const { data, error } = await safeMicroCmsGet('/blogs')
if (error) {
throw createError({ statusCode: error.statusCode, message: error.message })
}
return data
})// Cách 1: useFetch (recommended, auto SSR hydration)
const { data, error, status } = await useFetch('/api/blogs')
// Cách 2: useAsyncData (cần transform hoặc combine)
const { data } = await useAsyncData('blogs', () =>
$fetch('/api/blogs', { params: { limit: 10 } })
)
// Cách 3: $fetch trực tiếp (trong event handler, không cần SSR)
const data = await $fetch('/api/blogs')// Client-side
const [blogs, news] = await Promise.all([
$fetch('/api/blogs'),
$fetch('/api/news'),
])
// Server-side (dùng safe wrapper)
const [blogsResult, newsResult] = await Promise.allSettled([
safeMicroCmsGet('/blogs'),
safeMicroCmsGet('/news'),
])// API
interface ApiResponse<T> { data: T; status: number }
interface ApiError { message: string; statusCode: number }
// microCMS
interface MicroCmsListResponse<T> {
contents: T[]
totalCount: number
offset: number
limit: number
}
interface MicroCmsContent {
id: string
createdAt: string
updatedAt: string
publishedAt: string
revisedAt: string
}
// SEO options
interface SeoOptions {
title: string
description: string
ogImage?: string
canonicalUrl?: string
noindex?: boolean
}
// Breadcrumb / FAQ
interface BreadcrumbItem { name: string; url: string }
interface FaqItem { question: string; answer: string }yarn dev # Dev server tại http://localhost:3000
yarn build # Build production
yarn preview # Preview bản build
yarn lint # Kiểm tra lỗi ESLint
yarn lint:fix # Tự sửa lỗi ESLint
yarn format # Format code bằng Prettier