Skip to content

Commit a82c037

Browse files
committed
feat(content): update GitHub Copilot lessons article with new insights and images
1 parent 585780b commit a82c037

6 files changed

Lines changed: 240 additions & 5 deletions

File tree

.github/agents/nuxt.agent.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
name: "Nuxt Frontend"
3+
description: "Builds Vue SFC components, Nuxt file-based pages, and Nitro server routes for the TechWatching.dev personal website using Nuxt 4, @nuxt/ui, TailwindCSS, and @nuxtjs/seo."
4+
argument-hint: "[component or page] [requirements]"
5+
tools:
6+
- read
7+
- edit
8+
- search
9+
- execute
10+
- todo
11+
user-invocable: true
12+
---
13+
14+
You are the **Nuxt Frontend** — a frontend specialist that builds Vue SFC components, Nuxt file-based pages, and Nitro server routes for the TechWatching.dev personal website.
15+
16+
## Responsibilities
17+
18+
- Build Vue SFC components using `<script setup lang="ts">` and Composition API following the pattern in `app/components/` (e.g., `AppHeader.vue`, `GiscusComments.vue`, `ContactForm.vue`)
19+
- Implement Nuxt file-based pages in `app/pages/` with `useAsyncData` and `queryCollection` to fetch typed `@nuxt/content` data with path filters (e.g., `queryCollection('posts').path(route.path).first()`)
20+
- Compose page layouts with `@nuxt/ui` components (`UPageHero`, `UPageSection`, `UPageGrid`, `UPageCard`, `UContainer`, `UPageHeader`, `UPageBody`, `UContentToc`, `UContentSurround`, `UBadge`, `UButton`, `UAvatar`) and TailwindCSS utility classes
21+
- Configure per-page SEO using `useSeoMeta`, `useSchemaOrg` with `defineArticle` for blog posts, and `defineOgImageComponent('Saas', ...)` for OG image fallbacks when no cover image is available
22+
- Write Nitro server handlers in `server/api/` and `server/routes/` following the existing `.get.ts` naming convention (e.g., `tags.json.get.ts`, `feed.ts` pattern for RSS/Atom feeds)
23+
- Integrate third-party components: `@giscus/vue` for comments, `@stefanobartoletti/nuxt-social-share` for sharing, `@nuxt/image` `NuxtImg` for optimized images, PostHog analytics via `posthog-js`, and contact form via `submitjson`
24+
25+
## Technical Standards
26+
27+
- Always use `<script setup lang="ts">` — never use Options API or `defineComponent`
28+
- Import from `ufo` for URL joining: `import { joinURL } from 'ufo'` — use `joinURL(site.url, path)` for absolute URLs
29+
- Use `queryCollectionItemSurroundings('posts', route.path, { fields: ['description'] })` for prev/next navigation on post pages
30+
- Throw `createError({ statusCode: 404, statusMessage: 'Post not found', fatal: true })` when `queryCollection` returns null
31+
- Apply `view-transition-name` CSS for page transitions — `experimental.viewTransition: true` is already enabled in `nuxt.config.ts`
32+
- Server handlers must use `queryCollection(event, 'posts')` (pass `event` as first arg) when running in Nitro context
33+
34+
## Process
35+
36+
1. **Understand** — read the relevant existing page or component in `app/pages/` or `app/components/` to match structure and naming
37+
2. **Plan** — identify which `@nuxt/ui` components, composables, and content collections are needed
38+
3. **Build** — create/edit the `.vue` file with `<script setup lang="ts">`, compose the template with `@nuxt/ui`, add SEO metadata and schema.org markup
39+
4. **Verify** — run `pnpm typecheck` to confirm TypeScript types, then `pnpm dev` to test rendering
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
name: "nuxt-content-layer"
3+
description: "@nuxt/content v3 collection structure with numeric-prefix directory naming, zod-based defineCollection schemas in content.config.ts, YAML frontmatter conventions for posts (title, description, date, tags, badge, image, authors), and YAML index files for page-level content"
4+
applyTo: "content/**/*.{md,yml}"
5+
---
6+
7+
## Blog Post Frontmatter
8+
9+
- Every post in `content/1.posts/` MUST include all required fields from the `posts` collection schema in `content.config.ts`
10+
- Required fields: `title` (non-empty string), `description` (non-empty string), `date` (ISO date `YYYY-MM-DD`), `image.src` (media path), `badge.label` (category string)
11+
- Optional fields: `tags` (string array), `authors` (array with `name`, `to`, `avatar.src`), `canonical` (string URL for cross-posts)
12+
13+
```yaml
14+
---
15+
title: "Post Title Here"
16+
description: "One sentence describing the post content."
17+
date: 2026-03-09
18+
image:
19+
src: /images/placeholder_1.jpg
20+
badge:
21+
label: Tooling
22+
tags:
23+
- tag1
24+
- tag2
25+
authors:
26+
- name: Alexandre Nédélec
27+
to: https://twitter.com/techwatching
28+
avatar:
29+
src: /images/profile.png
30+
---
31+
```
32+
33+
## File Naming Convention
34+
35+
- Blog posts: numeric prefix + kebab-case slug — `78.post-slug.md` where 78 is the next available number
36+
- Goodies: no numeric prefix required — `tool-name.md` in `content/3.goodies/`
37+
- Determine next post number by reading the highest existing number in `content/1.posts/` and incrementing by 1
38+
- YAML collection pages use numeric prefix + name: `0.index.yml`, `1.posts.yml`, `2.speaking.yml`, `3.goodies.yml`
39+
40+
## Markdown Formatting
41+
42+
- Use ATX-style headings (`##`, `###`) — never underline-style
43+
- Images use Nuxt Content attribute syntax for TailwindCSS classes: `![alt text](/path/image.png){.rounded-lg .mx-auto}`
44+
- Code blocks include language and optional filename: ` ```typescript [filename.ts] `
45+
- Use `---` horizontal rules sparingly — only for major section breaks if needed
46+
47+
## Collection Schema in content.config.ts
48+
49+
- Always use `defineCollection` with explicit `source` glob and `type: 'page'`
50+
- Extend schemas with `createBaseSchema()` for new page-level collections that need `title` and `description`
51+
- Use `createLinkSchema()` for link objects, `createImageSchema()` for image objects with `src`, `alt`, `loading`, `srcset`
52+
- Field constraints use zod: `.nonempty()` for required strings, `.optional()` for optional fields, `.editor({ input: 'icon' })` for icon picker, `.editor({ input: 'media' })` for media picker
53+
54+
```typescript
55+
// Extending an existing schema
56+
posts: defineCollection({
57+
source: '1.posts/**/*',
58+
type: 'page',
59+
schema: z.object({
60+
tags: z.array(z.string()).optional(),
61+
image: z.object({ src: z.string().nonempty().editor({ input: 'media' }) }),
62+
date: z.date(),
63+
badge: z.object({ label: z.string().nonempty() })
64+
})
65+
})
66+
```
67+
68+
## YAML Index Files
69+
70+
- `0.index.yml` — home page structured data (hero, roles, sections, cta)
71+
- `1.posts.yml` — blog listing page metadata (align, links, image)
72+
- `2.speaking.yml` — speaking events array with `type` and `format` enums
73+
- `3.goodies.yml` — goodies listing page metadata
74+
75+
## Speaking Events
76+
77+
- `type` must be one of: `conference`, `meetup`, `podcast`, `webinar`
78+
- `format` must be one of: `talk`, `workshop`, `panel`, `keynote`, `lightning`
79+
- Fields: `name`, `date` (string), `event`, `type`, `format`, `location`, `url`, `slides`, `image`, `speakers` (string array)
80+
81+
```yaml
82+
events:
83+
- name: "Talk Title"
84+
date: "2026-03-09"
85+
event: "Conference Name"
86+
type: conference
87+
format: talk
88+
location: "City, Country"
89+
url: https://example.com
90+
```
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
name: "nuxt-frontend"
3+
description: "Vue 3 Composition API with <script setup lang=\"ts\">, Nuxt 4 file-based routing in app/pages/, @nuxt/ui component patterns, TailwindCSS utility classes, queryCollection for content fetching, and useSeoMeta/useSchemaOrg for SEO metadata"
4+
applyTo: "**/*.vue"
5+
---
6+
7+
## Component Structure
8+
9+
- Always use `<script setup lang="ts">` — never Options API, `defineComponent`, or `<script>` without `setup`
10+
- Place `<script setup>` before `<template>`, and `<style scoped>` last when needed
11+
- Use `const { data } = await useAsyncData(key, () => queryCollection(...))` — the key should be unique (use `route.path` for dynamic routes)
12+
- Destructure reactive refs directly: `const { data: post } = await useAsyncData(...)` not `const result = await useAsyncData(...); result.data`
13+
14+
```vue
15+
<script setup lang="ts">
16+
const route = useRoute()
17+
const { data: post } = await useAsyncData(route.path, () =>
18+
queryCollection('posts').path(route.path).first()
19+
)
20+
if (!post.value) {
21+
throw createError({ statusCode: 404, statusMessage: 'Post not found', fatal: true })
22+
}
23+
</script>
24+
```
25+
26+
## @nuxt/ui Component Usage
27+
28+
- Use `@nuxt/ui` v4 components: `UPageHero`, `UPageSection`, `UPageGrid`, `UPageCard`, `UPageHeader`, `UPageBody`, `UContainer`, `UPage`, `UContentToc`, `UContentSurround`, `UBadge`, `UButton`, `UAvatar`, `USeparator`
29+
- Pass `:ui="{}"` prop objects for layout customization — prefer TailwindCSS utility classes over custom CSS
30+
- Use `NuxtLink` for internal navigation and `NuxtImg` for all images (never `<a>` or `<img>` directly)
31+
- Use `NuxtImg` with explicit `width` and `height` props; add `loading="lazy"` for below-fold images
32+
33+
```vue
34+
<template>
35+
<UContainer>
36+
<UPageHeader :title="post.title" :description="post.description">
37+
<template #headline>
38+
<UBadge v-bind="post.badge" variant="subtle" />
39+
</template>
40+
</UPageHeader>
41+
<UPage>
42+
<UPageBody>
43+
<ContentRenderer :value="post" />
44+
</UPageBody>
45+
</UPage>
46+
</UContainer>
47+
</template>
48+
```
49+
50+
## SEO & Schema.org
51+
52+
- Call `useSeoMeta({ title, ogTitle, description, ogDescription })` in every page — read `seo` overrides from content first: `post.value.seo?.title || post.value.title`
53+
- Use `useSchemaOrg([defineArticle({ '@type': 'BlogPosting', ... })])` on post pages
54+
- For OG images: use `joinURL(site.url, post.image.src)` if a cover image exists; otherwise call `defineOgImageComponent('Saas', { headline: 'Blog' })`
55+
- Import `joinURL` from `ufo`, get site config via `const site = useSiteConfig()`
56+
57+
```vue
58+
<script setup lang="ts">
59+
import { joinURL } from 'ufo'
60+
const title = post.value.seo?.title || post.value.title
61+
useSeoMeta({ title, ogTitle: title, description, ogDescription: description })
62+
if (post.value.image?.src) {
63+
const site = useSiteConfig()
64+
useSeoMeta({ ogImage: joinURL(site.url, post.value.image.src) })
65+
} else {
66+
defineOgImageComponent('Saas', { headline: 'Blog' })
67+
}
68+
</script>
69+
```
70+
71+
## Content Fetching
72+
73+
- Use `queryCollection('collectionName')` — collection names match keys in `content.config.ts` exports: `index`, `blog`, `posts`, `goodies`, `goodiesPage`, `speaking`, `content`
74+
- Filter by path: `.path(route.path).first()` for single items, `.all()` for lists
75+
- Order posts by date: `.order('date', 'DESC').all()`
76+
- Use `queryCollectionItemSurroundings('posts', route.path, { fields: ['description'] })` for prev/next post navigation
77+
78+
## View Transitions
79+
80+
- Apply `view-transition-name` in `<style scoped>` for elements that animate between pages
81+
- The `experimental.viewTransition: true` is already enabled in `nuxt.config.ts` — no additional config needed
82+
83+
## Nitro Server Handlers
84+
85+
- Place handlers in `server/api/` (JSON responses) or `server/routes/` (custom response types like RSS)
86+
- Name files with HTTP method suffix: `tags.json.get.ts`, not `tags.json.ts`
87+
- In server context, always pass `event` as the first arg to `queryCollection`: `queryCollection(event, 'posts')`
88+
- Import from `@nuxt/content/server`: `import { queryCollection } from '@nuxt/content/server'`

content/1.posts/77.github-copilot-lessons-nuxt-blog.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
---
2-
title: 8 GitHub Copilot lessons learned upgrading my Nuxt blog
2+
title: 6 GitHub Copilot lessons learned upgrading my Nuxt blog
33
description: Practical insights and lessons from using GitHub Copilot
4-
date: 2026-03-01
4+
date: 2026-03-17
55
image:
6-
src: /images/placeholder_1.jpg
6+
src: /images/blog_1.jpg
77
badge:
88
label: Development
99
tags:
@@ -13,7 +13,7 @@ tags:
1313
- tooling
1414
---
1515

16-
Last December, I upgraded my Nuxt blog with the help of GitHub Copilot and I learned 8 lessons about using AI-assisted development that I want to share.
16+
Last December, I upgraded my Nuxt blog with the help of GitHub Copilot and I learned 6 lessons about using AI-assisted development that I want to share.
1717

1818
## Plan your changes
1919

@@ -95,16 +95,34 @@ With git worktrees, you can checkout multiple branches at the same time in diffe
9595

9696
However, when git worktrees really shine is when you want to parallelize work across multiple branches using multiple AI agents. Most of the AI assistants have built-in support for delegating tasks to agents in a specific worktree, so that every agent can work on its own branch without interfering with the others.
9797

98-
![Image description](/posts/images/77.github-copilot-lessons-nuxt-blog_1.png){.rounded-lg .mx-auto}
98+
![Background Agent with worktree](/posts/images/77.github-copilot-lessons-nuxt-blog_1.png){.rounded-lg .mx-auto width="800"}
9999

100100
That's something I really recommend using especially to avoid context switching. When you are focused on a specific task, and you spot something to fix that is not related to your current task, you can just delegate it to a background agent that would work in a different worktree without interrupting your flow or messing with your file.
101101

102102
## Try different LLMs
103103

104104
Models keep improving and different companies are producing competitive new models. Don't hesitate to try new models, to use different types of models (faster, more accurate, etc.) depending on the context or the task. If you are not satisfied with what has implemented you favorite LLM on a task, try to do the same task again with a different model to get better results.
105105

106+
It's exactly what I did for my migration, even for the plan to have different perspectives on the migration steps.
107+
108+
::callout{icon="i-heroicons-light-bulb"}
106109
An interesting idea could be to use git worktrees to implement the same task with different models on different branches at the same time.
110+
::
107111

108112
## WYPIWYG with an integrated browser
109113

114+
You are probably familiar with the WYSIWYG acronym: "What You See Is What You Get" for software that allows you to edit document visually. With AI-assisted development, we are in a world of WYPIWYG: "What You Prompt Is What You Get" (it seems that other people have used this acronym before but I can't find who is the original author to credit). And thanks to the integrated browser in IDEs like VS Code, not only you can see the results of your prompts in real time, but the agents also have acess to the browser and can interact with it.
115+
116+
With tools like Playwright MCP or the Chrome DevTools MCP, I initially thought that the VS Code integrated browser (previously Simple Browser) was useless for development. But when migrating my website, I realized it was a must have. I was able to select an element on a page, ask GitHub Copilot change its layout or appearance, and see the result in real time in the integrated browser. To quickly iterate on the design of the website, it was amazing.
117+
118+
119+
![VS Code Integrated Browser](/posts/images/77.github-copilot-lessons-nuxt-blog_2.png){.rounded-lg .mx-auto}
120+
121+
122+
::callout{icon="i-heroicons-light-bulb"}
123+
I believe that this could lead to new kinds of workshops where developers and business people could collaborate together on the design of a website or an application.
124+
::
125+
126+
## Final thoughts
110127

128+
In the end, using AI-assisted development is just a question of context engineering and know how to use your tooling. The hard part is to keep up to date with the latest changes as the ecosystem is evolving faster than ever.

public/images/blog_1.jpg

425 KB
Loading
166 KB
Loading

0 commit comments

Comments
 (0)