Exclusive tips every week

Join 13,567+ other Vue devs and get exclusive tips and insights delivered straight to your inbox, every week.

    Picture of Michael Thiessen

    👋Hey friend! I work hard to send you amazing stuff each week.

    — Michael

    I really love and enjoy reading these emails.

    You are one of the most pro VueJS devs I know, and I am happy that you share this knowledge.

    Fabian Beer

    Here's my latest newsletter

    🔥 (217) Default Content and Extension Points, Fine-Grained Loading, and Configurable Composables

    Hey!

    I've got a few new Nuxt articles for you (I've been busy):

    If you're interested in learning more about component design patterns, Clean Components Toolkit is 35% off until May 22! You can grab it here: Clean Components Toolkit

    Every week new lessons are being dropped for Mastering Nuxt! Yesterday, we started on the data fetching chapter where we wrap up all basic functionality on the app.

    Have a fantastic week!

    — Michael

    🔥 Default Content and Extension Points

    Slots in Vue can have default content, which allows you to make components that are much easier to use:

    <button class="button" @click="$emit('click')">
    <slot>
    <!-- Used if no slot is provided -->
    Click me
    </slot>
    </button>

    My favourite use for default slots is using them to create extension points.

    Basically, you take any part of a component, wrap it in a slot, and now you can override that part of the component with whatever you want. By default, it'll still work the way it always has, but now you have more options:

    <template>
    <button class="button" @click="$emit('click')">
    <!-- Adding in the slot tag does nothing at first -->
    <!-- We can override this by providing content to the slot -->
    <slot>
    <div class="formatting">
    {{ text }}
    </div>
    </slot>
    </button>
    </template>

    Now you can use this component in many different ways. The easy, default way, or your own, custom way:

    <!-- Uses default functionality of the component -->
    <ButtonWithExtensionPoint text="Formatted text" />
    <!-- Use the extension point to create custom behaviour -->
    <ButtonWithExtensionPoint>
    <div class="different-formatting">
    Do something a little different here
    </div>
    </ButtonWithExtensionPoint>

    🔥 Fine-grained Loading API in Nuxt

    In Nuxt we can get detailed information on how our page is loading with the useLoadingIndicator composable:

    const {
    progress,
    isLoading,
    } = useLoadingIndicator();
    console.log(`Loaded ${progress.value}%`); // 34%

    It’s used internally by the <NuxtLoadingIndicator> component, and can be triggered through the page:loading:start and page:loading:end hooks (if you’re writing a plugin).

    But we have lots of control over how the loading indicator operates:

    const {
    progress,
    isLoading,
    start, // Start from 0
    set, // Overwrite progress
    finish, // Finish and cleanup
    clear // Clean up all timers and reset
    } = useLoadingIndicator({
    duration: 1000, // Defaults to 2000
    throttle: 300, // Defaults to 200
    });

    We’re able to specifically set the duration, which is needed so we can calculate the progress as a percentage. The throttle value controls how quickly the progress value will update — useful if you have lots of interactions that you want to smooth out.

    The difference between finish and clear is important. While clear resets all internal timers, it doesn’t reset any values.

    The finish method is needed for that, and makes for more graceful UX. It sets the progress to 100, isLoading to true, and then waits half a second (500ms). After that, it will reset all values back to their initial state.

    🔥 Configurable Composables

    We can make our composables more reusable by passing in an object that contains all of the configuration options for how we want the composable to behave:

    const state = ref({ email: '' });
    const { history, undo, redo } = useRefHistory(state, {
    // Track history recursively
    deep: true,
    // Limit how many changes we save
    capacity: 10,
    });

    We use an object here instead of a long list of parameters:

    const { history, undo, redo } = useRefHistory(state, true, 10));

    Using an options object instead of parameters gives us several benefits.

    First, it’s self-documenting. We have the name of the parameter right beside the value, so we never forget what each value is doing.

    We can also create a type for the entire options object:

    export type RefHistoryOptions {
    deep?: boolean;
    capacity?: number;
    };
    export type RefHistoryReturn {
    history: Ref;
    undo: () => void;
    redo: () => void;
    };
    export function useRefHistory(
    ref: Ref,
    options: RefHistoryOptions
    ): RefHistoryReturn {};

    Second, we don’t need to worry about ordering or unused options. The more potential edge cases we cover with a composable, the more options we’ll have. But we usually only need to worry about a couple of them at one time — they’re all optional.

    Third, it’s much easier to add new options. Because the order doesn’t matter and none of the options are required, adding a new capability to our composable won’t break anything. We simply add it to the list of possible options and carry on.

    The pattern doesn’t require a lot of work to implement, either:

    export function useRefHistory(ref, options) {
    const {
    deep = false,
    capacity = Infinity,
    } = options;
    // ...
    };

    First, we pass in the options object as the last parameter. This makes it possible to have the options object itself as an optional parameter.

    The required params come first. Typically, there will only be one or two. More parameters is a code smell, and likely means that your composable is trying to do too much.

    The required parameter (or parameters) is very often a Ref, or a MaybeRef if we’re also implementing the Flexible Arguments Pattern.

    We then access the options by destructuring.

    Doing this gives us a really clean and readable way of providing defaults. Remember, these are options so they should all have defaults. If the values are required they should likely have

    This helps to clarify what options are being used in this composable. It’s not uncommon for one composable to use another composable, and in that case some of the options are simply passed along to the inner composable:

    export function useRefHistory(ref, options) {
    const {
    deep = false,
    capacity = Infinity,
    ...otherOptions,
    } = options;
    // Pass along some options we're not using directly
    useSomeOtherComposable(otherOptions);
    };

    If you want to learn more patterns for composables, check out Composable Design Patterns.

    📜 3 Kinds of Props in Vue

    One of Vue's core features is the use of props. Props are how we pass data around in Vue, from parent to child components.

    But not all props are created equal.

    There are three main kinds:

    • Template Props
    • Configuration Props
    • State Props (or Data Props).

    Check it out here: 3 Kinds of Props in Vue

    The NuxtLink component may seem simple at first glance, but there’s a lot going on beneath the surface.

    It’s one of the easiest Nuxt components to use, while giving our apps a big performance boost.

    In this article we see some things about NuxtLink you may not have known.

    Check it out here: Better Navigation with NuxtLink

    📅 Upcoming Events

    Here are some upcoming events you might be interested in. Let me know if I've missed any!

    VueConf US 2025 — (May 19, 2025)

    VueConf US 2025 is a great Vue conference, this year held in Tampa from May 19–21, with two days of talks and a day of workshops. Unfortunately, I am no longer able to make it to the conference this year. I hope everyone attending has an amazing time and I look forward to joining in the future!

    Check it out here

    MadVue 2025 — (May 29, 2025)

    It's time to get together in Madrid. Join for a full day of talks, activities, and networking with the Vue.js community and ecosystem.

    Check it out here

    💬 Unhappy

    "You're bound to be unhappy if you optimize everything." — Donald Knuth

    🧠 Spaced-repetition: Make Testing Easy

    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.

    Testing is important to do, but it can be hard to do.

    In my experience, good architecture lends itself to easy-to-write tests (or at least, easier-to-write). The inverse is also true, that difficult-to-write tests are typically a symptom of poor architecture.

    Of course, sometimes tests are just hard to write, and there’s no way around it.

    The best thing we can do is borrow a tool from mathematics and science, and transform a difficult problem into an easier but equivalent one:

    • Humble Components — UI is notoriously hard to test, and always has been. So keep as much in Humble Components, components that only receive props and emit events and nothing else. By making our UI as simple as possible we also make it much easier to test.
    • Extract logic to composables — And I mean all of your logic. Components (that aren’t Humble) should only contain the bare minimum to connect all the different composables together. Think of them as Controller Components, the “C” in MVC.
    • Composables are thin layers of reactivity — The easiest thing in the world to test are pure functions that have no dependencies. If you can make the majority of your codebase simple JS or TS code, you’ve already won. Composables then become simple wrappers that add a layer of reactivity to this business logic.

    Michael Hoffman curates a fantastic weekly newsletter with the best Vue and Nuxt links.

    Sign up for it here.

    p.s. I also have a bunch of products/courses:

    Here's what others are saying

    I'm starting to think that your newsletter is one of the best things that happened to me this year. I just love these emails.
    Stanislaw Gregor
    I'm somewhere in the upper-middle level at Vue, and I never miss an email you and always find something cool when reading!
    Eduard Climov
    This is the first time where I'm actually enjoying email newsletters. I like your format a lot.
    Fahmi Alfituri
    You have great content in your emails. I seriously learn something from every one of them.
    Titus Decali
    Just wanted to say I enjoy these emails. They encourage me to constantly improve my craft. Fantastic work.
    Joe Radman
    Thanks for another beautiful tip 🙏
    Victor Martins Onuoha
    Loving these, and the spaced repetition.
    Mark Goldstein
    I really enjoy reading your emails, because I love Vue. Thanks for these emails.
    Arturo Espinoza
    I really love and enjoy reading these emails. You are one of the most pro VueJS devs I know, and I am happy that you share this knowledge.
    Fabian Beer
    THANK YOU! I did not know about the deep property, so I assumed you simply couldn't watch objects.
    Darryl Noakes
    I really must say you are doing a really great job. Now I am aware of a cleaner and more performant way to use Tailwind. Thanks a lot!
    Henry Eze
    Thank you so much, I really enjoy and appreciate your emails.
    Carlos Gonzalez
    Thanks for sharing great Vue tips.
    Fernando Navarro
    I really enjoy these tips.
    Martin H
    Thank you so much for the weekly Vue education. Thanks and live on longer to educate us more.
    Kabolobari Benakole
    I look forward to your emails every week. This week was something I knew, but I like to be reminded of. Thanks for keeping it up!
    Nathan Strutz
    Thank you for your weekly emails. I always look forward to learning a new tip about Vue or at least relearning old tips :)
    Colin Johnson
    I have really been enjoying your weekly emails, and I also got my two team members to join in on the fun as well.
    Keith Dawson
    Thank you for your newsletter, your explanations have very concise examples and I like it.
    Nicolas Decayeux
    Thanks A LOT for your great work!!! One of the few newsletters that I let pass!
    Martin Tillmann

    Want to level up your Vue skills?

    With over two million reads and 11,067 subscribers, you've come to the right place.

    Subscribe now to get exclusive insights and tips every week.