🔥 (#147) Nuxt content queries and the hardest part of web apps
Hey there!
I hope your January and the beginning of 2024 is going well.
Yesterday I launched the second edition of Vue Tips Collection!
So far the response has been overwhelming (but in a good way 😂).
It's on a 35% discount until Friday.
I also only have 30 hardcovers to sell, and they're going quickly.
You can check it out here: Vue Tips Collection
But you're here for the tips, so here they are.
Enjoy, and have a wonderful day!
— Michael
🔥 Nuxt Content Queries
Nuxt Content 2 gives us an effortless way to query our content using the queryContent
method:
// composables/useArticles.js export default () => queryContent('articles') .where({ // Optional fields that may be true or non-existent ghost: { $ne: true }, newsletter: { $ne: true }, // Don't render articles scheduled for the future date: { $lte: new Date() }, }) .only(['title', 'path', 'description', 'date', 'tags']) .sort({ date: -1 }) .find();
Here, I've created a composable called useArticles
for my blog, which grabs all of the content inside of the content/articles/
directory.
The queryContent
composable is a query builder, which gives us a lot of expressiveness in what data we fetch. Let's see how we're using this here.
First, we're using a where
clause to filter out all the articles we don't want. Sometimes I will add an article before I want it to be "published" to the site.
I do this by setting the date
in the future and then only taking articles before "today" using this clause:
date: { $lte: new Date() }
Second, some articles are the newsletters I write each week. Others are pieces of content that I want to keep in the articles
folder but don't want to be published.
I use frontmatter fields to specify this:
--- newsletter: true # This is a newsletter ---
--- ghost: true # This content won't appear on the site ---
Third, we use the only
clause to grab just the fields we need. By default, the queryContent
method returns a lot of data, including the entire piece of content itself, so this can make a big difference in payload size.
Lastly, as you have probably guessed, we have a sort
clause to sort the articles so the most recent ones appear last.
The queryContent
composable has more options than this, which you can read about on the docs.
🔥 Default Content with Slots
You can provide fallback content for a slot, in case no content is provided:
<!-- Child.vue --> <template> <div> <slot> Hey! You forgot to put something in the slot! </slot> </div> </template>
This content can be anything, even a whole complex component that provides default behaviour:
<!-- Child.vue --> <template> <div> <slot name="search"> <!-- Can be overridden with more advanced functionality --> <BasicSearchFunctionality /> </slot> </div> </template>
🔥 How I deal with dynamic classes
A pattern I use constantly is triggering classes with boolean
flags:
<template> <div :class="disabled && 'disabled-component'"> Sometimes this component is disabled. Other times it isn't. </div> </template> <style scoped> /* Some styles */ .disabled-component { background-color: gray; color: darkgray; cursor: not-allowed; } </style>
Either the trigger is a prop I use directly, or a computed prop that tests for a specific condition:
disabled() { return this.isDisabled || this.isLoading; }
If I just need one class on an element, I use the logical AND to trigger it:
<div :class="disabled && 'disabled-component'"></div>
Sometimes it's a decision between two classes, so I'll use a ternary:
<div :class="disabled ? 'disabled-component' : 'not-yet-disabled'" />
I don't often use more than two classes like this, but that's where an Object
or Array
comes in handy:
<div :class="{ primary: isPrimary, secondary: isSecondary, tertiary: isTertiary, }" /> <div :class="[ isPrimary && 'primary', isSecondary && 'secondary', isTertiary && 'tertiary', ]" />
Of course, when it gets complex enough it's better to just have a computed prop that returns a string of class names (or returns an Object
or Array
):
<div :class="computedClasses" />
🐦 Trending Tweet
I don't often share tweets (posts?) in this newsletter, but I thought the discussion here was interesting.
What are your biggest challenges?
|
📜 Client-Side Error Handling in Nuxt 3
It may not be as fun as shipping the next feature, but making sure our apps are rock-solid and can recover from errors is crucial.
Without good error-handling, our UX suffers and our applications can be hard to use.
In this article I explore handling client-side errors using the NuxtErrorBoundary
component.
Check it out here: Client-Side Error Handling in Nuxt 3
💬 More Reliable Features
"If there is a feature of a language that is sometimes problematic, and if it can be replaced with another feature that is more reliable, then always use the more reliable feature." — Douglas Crockford
🧠 Spaced-repetition: Controlled Props — or How to Override Internal State
The best way to commit something to long-term memory is to periodically review it, gradually increasing the time between reviews 👨🔬
Actually remembering these tips is much more useful than just a quick distraction, so here's a tip from a couple weeks ago to jog your memory.
Here we have a simple Toggle
component that can show or hide content:
<template> <Toggle title="Toggled Content"> This content can be hidden by clicking on the toggle. </Toggle> </template>
It keeps track of its own open
state internally right now.
But what if we want to override that internal state, but only some of the time?
To do this, we have to dynamically switch between relying on props and events, and relying on the internal state:
export default { name: 'Toggle', props: { title: { type: String, required: true, }, hidden: { type: Boolean, // Must be set to `undefined` and not `false` default: undefined, } }, data() { return { // Internal state _hidden: false, }; }, methods: { toggleHidden() { // Switch between emitting an event and toggling state if (this.hidden !== undefined) { this.$emit('toggle-hidden'); } else { this._hidden = !this._hidden; } }, }, computed: { $hidden() { // Dynamically switch between state or prop return this.hidden !== undefined ? this.hidden : this._hidden; }, }, };
In the Toggle
component we now have to use the $hidden
computed prop:
<template> <div> <div class="title" @click="toggleHidden" > {{ title }} </div> <slot v-if="$hidden" /> </div> </template>
You can check out a more detailed tutorial on my blog.
p.s. I also have four products/courses: Clean Components Toolkit, Vue Tips Collection 2, Mastering Nuxt 3, and Reusable Components
评论
发表评论