[{"id":"content:0.index.md","path":"/","dir":"","title":"Open Source Developer","description":"Open source developer, contributing to the Vue, Nuxt, and Vite ecosystems.","keywords":[],"body":" Harlan Wilton Hey, I'm Harlan. An open-source developer from Sydney, Australia . Core team member of Nuxt ,\n VueUse and\n UnJS . Author of Unlighthouse , Unhead and Nuxt SEO . Recent Personal Updates "},{"id":"content:1.blog.md","path":"/blog","dir":"","title":"Blog","description":"This is a directory of the articles I have written and published that are sometimes updated.","keywords":[],"body":" "},{"id":"content:2.projects.md","path":"/projects","dir":"","title":"Projects","description":"This is a directory of the open-source packages and tools I've released that are actively maintained.","keywords":[],"body":" This is a directory of the open-source packages and tools I've released that are actively maintained. You can find all of my projects on GitHub . "},{"id":"content:3.sponsors.md","path":"/sponsors","dir":"","title":"Sponsors","description":"Through the support of my amazing sponsors I'm able to dedicate a lot of my professional time to working on open-source.","keywords":["Send some love"],"body":" Through my amazing sponsors, I'm able to dedicate a lot of my time to working on open-source. Thank you to all of them! Send some love Enjoy my work? Heck yeah! You can show your support by following my Twitter ,\njoining my Discord \nor paying for my coffee addiction ."},{"id":"content:4.talks.md","path":"/talks","dir":"","title":"Talks","description":"This is a directory of the talks I've given either in video or slide format.","keywords":["Videos","Slides"],"body":" Videos Supercharged SEO head management for Nuxt + Practical SEO tips: Nuxt Nation 2022 Slides VueFes 2023 - Getting your head around your
Nuxt Nation 2022 - Supercharged SEO head management for Nuxt + Practical SEO tips Laravel Meetup 2022 - To Vite and Beyond: A history and future of bundling"},{"id":"content:5.meet.md","path":"/meet","dir":"","title":"Meet","description":"Let's meet up virtually or in real life.","keywords":["IRL Schedule"],"body":" Let's meet Meeting new people is one of my favourite things about open-source. I love chatting about open-source, business,\ncraft-beer or anything. If you want to chat, feel free free to book a video call with me. Otherwise,\njust jump in my Discord and say hi. IRL Schedule I'm around Sydney, Australia for the next couple of months. Hit me up if you feel like catching up!"},{"id":"content:6.hire.md","path":"/hire","dir":"","title":"Hire","description":"This is a directory of the articles I have written and published that are sometimes updated.","keywords":[],"body":" When not creating open-source, I work for myself as a developer for hire. I specialise in building sites that: Users and search engines love Scale to complex architecture requirements I'm passionate about UX, SEO and performance and my stack is Nuxt and Laravel. My standard rate is $120 USD / hour. Email me any exciting opportunities you have."},{"id":"content:blog:0.2023-february.md","path":"/blog/2023-february","dir":"blog","title":"February 2023: GitHub Sponsor perks and firing a freelance client","description":"I share my updates for February covering my open-source development, financials and personal updates.","keywords":["GitHub Sponsors Perk Updates","Month Review"],"body":" Hello, my amazing sponsors! Welcome to first ever newsletter. I have some exciting things to share with you. GitHub Sponsors Perk Updates Going forward, I'll be improving your sponsor perks in three ways. Early Access Monthly Newsletter At the start of each month, I'll be sending a newsletter (just like this one). This will cover my achievements, financials, and plans for the month ahead. I'll post these publicly towards the end of the month on my blog. New Discord Role / Channels I have a Discord server that I use to provide support to many of my open-source packages. If you have any technical issues you'd like help on, feel free to ask here. I have the following new perks within discord: New role: \"sponsor\" New discussion channel: 💎・priority-support. Link to join: https://discord.gg/FC8FgHuC Bi-monthly Video Call Every 2 months, I'm going to schedule a time to do a video call with any sponsors who would like to join. In it, you can ask me any questions or just hang out, no pressure smile 30-60 minutes Held in discord (link above) First call is scheduled for 9pm AEDT, calendar invite link Month Review Achievements My personal, open-source and freelancing achievements. OSS ⭐ 3431 GitHub stars (+231) Unhead v1.1 release, many performance and security improvements Nuxt OG Image Satori Support and OG Image Playground Personal 🍴 I started the month with a 120-hour fast (5 days)! Went pretty well, and lost 4kg ✈️ Gave notice to move out of our apartment at the end of March Sponsorship Impressions (sites only) The analytics of people who see your generous sponsorship on my doc / personal sites. 👨 5.7k visitors (-22)\n👀 26.3k views (+4k) Financials How sustainable is my current open-source work My financial goal for February was saving $10k, as I have some travel plans coming up. I planned to spend 50% of my time on open-source. I had a ~100-hour project booked with one of my clients. Around 40 hours into the project it was clear that I couldn't work with them anymore on it. I decided to fire them and ended up refunding them 40 hours of work to be done with it. While this was quite a hit, the peace of mind of not having to work with them anymore on it was worth it. I'll avoid any bad-mouthing, I'll just say that they weren't pleasant to work with. OSS ⌛ 111 hrs 💸 A$617.06 A$5.55 / hour Freelancing ⌛ 78.25 hrs (+ 35.25 hrs) 💸 A$3015 (- $517.5) A$38.53 / hour (-$42.67) March plans By the end of this month, I'll be starting my travels for 3 months in Asia, going from Thailand, South Korea, and Indonesia. During this time, I'll continue to work on open-source, just while sitting on a beach drinking a coconut. Until then I have some goals: Significantly improve the Unhead performance in Nuxt (feat: server/client only composables, feat(head): improved Unhead integration) Docs site for Nuxt SEO Kit Make Nuxt OG Image an official Nuxt module, have a few bugs to fix Land a few more Nuxt PRs Summary I hope you've enjoyed the first newsletter! Again, please join me for the first sponsors call on the 7th if you're interested. Best wishes to all of you and I hope you have an amazing month ahead."},{"id":"content:blog:0.my-open-source-journey.md","path":"/blog/my-open-source-journey","dir":"blog","title":"Introduction: My open-source journey","description":"Discover how you can build a modern package development through how I created the Unlighthouse project.","keywords":["My Open Source Journey"],"body":" My Open Source Journey At the end of 2020, I finished up my full-time job at a startup . I wanted to explore other ways of working,\nwith the idea of building a SaaS product. Though, without an idea, I was passionate about and no audience. It felt like a waste of time. Instead, I decided to focus on learning as much as I could within the Vue ecosystem. I started building my old blog \nin the earliest version of VitePress and did an article comparing Vite to webpack . In setting up Twitter and sharing that article, I was hooked. I started writing more, contributing issues and building my own Vue plugin . Through these I grew my audience and started focusing on Nuxt modules and joined the WindiCSS team. Since then, I've been working on all sorts of projects. While progress has been slow while I've been learning, I'm proud of what I've accomplished,\nand I'm excited to share more with the community."},{"id":"content:blog:1.2023-march.md","path":"/blog/2023-march","dir":"blog","title":"March 2023: Planning a year of travel and moving out","description":"I share my updates for March covering my open-source development, financials and personal updates.","keywords":["Personal Updates","Numbers","Work Updates / Financials","April plans","Final thoughts"],"body":" Personal Updates Moving out Since becoming a freelancer and focusing on open-source, my days are mostly relaxed. I can choose what I want to work\non and actively avoid any hard deadlines. March was the complete opposite. Deadlines on moving out, on planning and preparing for a year of travel, finishing off freelancing commitments\nand staying on top of my open-source work. But it all got done. As of the end of March, my girlfriend (Alina) and I handed over the keys to our leafy-green apartment in Canberra, Australia.\nWith no where in particular to now call home. Travel We started our travel last week, landing in Bangkok, Thailand. Bangkok is a hectic city, not recommended for relaxing.\nBut I enjoyed the city, the food and the people. It has been interesting trying to work on open-source while travelling. Any down-time I have I whip out the laptop. Thailand's internet has been surprisingly good which as made that easier. As of writing this we're in Ko Chang, Thailand. Ko Chang is a dense mountainous jungle island with deep-blue water paired with complex orange gradient sunsets. We're staying in a bungalow near the beach for $30 AUD a night. Numbers ⭐ 3580 GitHub stars (+149) Work Updates / Financials With the cost of living in Thailand much cheaper than Australia, I'm spending less money while travelling than I would be\nif I was still living in Canberra. Open-Source ⌛ 88 hrs (-23 hrs) 💸 $787 AUD (+$168) $8.94 AUD / hour (+$3.39 / hour) Most of my open-source time last month went into Nuxt modules and Nuxt itself. My current focus is trying to get all the Nuxt SEO Kit modules to a v2 and release v2 of Nuxt SEO Kit. Main projects 24 hours Nuxt - working on better Unhead integration, a couple of minor bugs and triage 22 hours nuxt-simple-sitemap - v2 with many SSR improvements and bug fixes. Is now closer to being feature-complete. 14 hours Unhead - working towards a v2 which will significantly reduce the bundle size. 12 hours nuxt-og-image - v2 is out under a beta tag, with many bug fixes and improvements. Still want to add a couple of features for the v2 release. 3 hours nuxt-simple-robots - v2 is out and now feature-complete. Freelancing ⌛ 6 hrs (-72.25 hrs) 💸 $645 AUD (-$2370) $107 AUD / hour (+$68 / hour) Didn't have too much freelancing to do. Finished off some important projects and now have a bit of a break. April plans Figuring out what I can actually finish while travelling is a challenge. I'm trying to keep my goals small and achievable. I would like to get Nuxt SEO Kit v2 out by the end of April. This will be quite a bit of work as each dependent module\nneeds to be updated to v2, which I have many plans for. It also includes a proper documentation site. I would also like to get the Unhead v2 update out. The bundle size improvements will be a win for Nuxt and the Vue ecosystem. Final thoughts I'm really enjoying travelling and working while travelling. I hope to sustain it as long as I can and I can't thank you,\nthe sponsors, enough for your support. While the amount I can get done will take a hit, I'm still dedicated to providing as much value\nas I can for your contributions. Please get in touch with my if you have any questions, feedback or suggestions. Would love to chat!"},{"id":"content:blog:2.2023-april.md","path":"/blog/2023-april","dir":"blog","title":"April 2023: Thai New Year and Nuxt SEO Progress","description":"April was spent traveling within Thailand while working through a bunch of bugs in my Nuxt SEO modules.","keywords":["Personal Updates","Work Updates","Numbers / Financials","May plans","Final thoughts"],"body":" Personal Updates A Month In Thailand We spent all of April in Thailand, 38 days total.\nWe got lucky to sneak into Thailand just before they removed the 45-day visa on arrival. As you'll know from the last update , we started the month in Ko Chang. For those interested in our route afterward: Chanthaburi - A small and quiet town with charm, known for its gemstones. Pattaya - Caught up with some friends here to celebrate Thai New Year. Krabi - Amazing rock formations. Would highly recommend mangrove canoeing. Ko Phi Phi - Tiny scenic island but very touristy. Think bucket cocktails for $4. Khao Sok - Jungle, caves, lakes, monkeys and waterfalls. One of my favourite places in Thailand. Chiang Mai - Super-chill and relaxed city. Great food, great people, great vibes. Pai - Quite touristy with a hippy vibe. The surrounding nature is amazing. Thai New Year 🇹🇭 We were lucky enough to be in Thailand for Songkran. Songkran is a festival to mark the start of the Buddhist New Year.\nWater is thrown to symbolize the washing away of sins and bad luck. We spent the holiday hanging out with our friends and the Thai locals,\nthrowing icy-cold water at passers-by on the street. Work Updates Nuxt SEO Kit v2 Module Progress Getting v2 of Nuxt SEO Kit has been weighing on me for a while. Making progress towards it meant solving some particularly annoying bugs. Most of my time was spent on nuxt-og-image . It has been a challenging module. Debugging WASM code, sifting through nitro code and testing on a number of cloud providers. I went through 30 beta releases and things are looking better. The rest went into nuxt-simple-sitemap . In March, I decided to add support for i18n. I18n is complicated. While there is still some more work to do it's a lot more stable and the improvements will be incremental. Numbers / Financials ⭐ 3779 GitHub stars (+199) Open-Source ⌛ 70.75 hrs (-17.25 hrs) 💸 $1,013 AUD (+$226) - Had a payout from the Windi CSS project as it was sunset. $14.32 AUD / hour (+$5.38 / hour) - Highest yet! Although this was mostly a one-off. Main projects 34 hours nuxt-og-image 16.5 hours nuxt-simple-sitemap 6 hours Nuxt Freelancing ⌛ 2.25 hrs (-4.75 hrs) 💸 $225 AUD (-$420) $100 AUD / hour (-$7 / hour) April's focus was on open-source work. I had two organic leads come in.\nI decided to push the meetings to May where I had more availability while traveling. May plans In April, I wanted to get Nuxt SEO Kit v2 out. This was optimistic and unrealistic, unfortunately. May's plan is to continue the work towards it.\nThe estimate for a Nuxt SEO Kit v2 stable release is still a few months away. Final thoughts It was a really fun month in Thailand, I had a good balance of work and traveling.\nIt's never satisfying just working on bugs and issues, there's not much to show for it. But this is the way open-source works it seems.\nPeriods of rapid development and then periods of slow but steady maintenance to get things stable. Thanks for reading and thanks to my amazing sponsors."},{"id":"content:blog:4.2023-may.md","path":"/blog/2023-may","dir":"blog","title":"May 2023: South Korea Mountains and Unlighthouse Goes Viral","description":"May was spent traveling to South Korea and Indonesia while working on Unlighthosue going viral.","keywords":["Personal Updates","Work Updates","Numbers / Financials","June plans","Final thoughts"],"body":" Personal Updates South Korea Travel In May, we finished up our time in Thailand and traveled to South Korea for 2 weeks. Seoul - Amazing city with so much to do. The culture is very unique and the mountains surrounding the city are beautiful. Busan - Coastal city, kind of reminded me of the Gold Coast in Australia. Jeju - Island off the south coast of South Korea. Beautiful beaches and hiking. We really loved the food, especially the Korean BBQ and bipimbap. They had surprisingly good coffee, craft beer and pastries, which I partook in generously. Hiking Bukhansan Mountain in Seoul and Hallasan Mountain in Jeju were definitely the highlights. I would love to return one day and explore more of the country. Indonesia Travel After South Korea, we flew to Jakarta, Indonesia with a quick stop over in Singapore.\nWe'll spend the next month here, until mid-June when we head back to Australia. We're taking Indonesia pretty slow.\nIt's nice to have a bit of a break after the busy itinerary in South Korea and Thailand. Yogyakarta - University town, lots of culture, art and history. Hostels here have a really nice, social vibe. Bali - Ubud and Canggu.\nCanggu is very touristy, but the food is worth it.\nMelbourne Cafe tier.\nUbud is quite touristy too, but you can see some\namazing sights around like rice fields, temples and waterfalls. Gili Islands - Good snorkeling and chilling. (currently here) Flores - Planning lots of hikes for here. We're looking forward to Komodo National Park and Kelimutu volcano. Work Updates Unlighthouse Goes Viral Last month I had a DM from someone telling me that they had put Unlighthouse front of Jeff from Fireship. I thought that was pretty cool in of itself, and I didn't think much more about it. Next thing I knew, though, the Unlighthouse stars started going bananas. I did some quick research and found the culprit. Very cool. And with that, I had a massive influx of issues to deal with through GitHub and my Discord. I started working through them, I would solve one, and two more would appear.\nIt took me the rest of my May to get control of them. It wasn't a bad problem to have though.\nI'm grateful to have Unlighthouse being used by so many people, and I'm glad it's helping people improve their sites. It's given me a lot of ideas for how to improve it, and I'm excited to work on it more in the future. Numbers / Financials ⭐ 5407 GitHub stars (+1628) Open-Source ⌛ 84 hrs (+13.25 hrs) 💸 $744 AUD (-$269) - Payout from Windi CSS the month before. $8.85 AUD / hour (-$5.46 / hour) Main projects 27 hours Unlighthouse Some important improvements around reporting, authentication, how the chrome binary is used and Docker. 23 hours nuxt-seo-kit Extracting the breadcrumbs and site config logic into separate modules in preparation of v2. These will be released soon. 8 hours nuxt-og-image Getting the final issues solved so the v2 release can happen. 7.5 hours nuxt-simple-sitemap Some outstanding issues around runtime sitemaps and i18n. Freelancing ⌛ 8.25 hrs (+0.75 hrs) 💸 $1,025 AUD (+$800) $124.24 AUD / hour (+24.24 / hour) I started some SEO work for a new client. The work was to fix site-wide technical issues from Google Lighthouse.\nThis is exactly why I originally built Unlighthouse,\nso it was perfect. June plans I'll be landing back in Australia toward the middle of June. I look forward to having a few weeks of routine.\nI'll use this time to focus on getting Nuxt SEO Kit v2 released. Final thoughts I'm really grateful to see the growth in Unlighthouse this month,\nand I'm excited to finally release what I've been working on the for the last few months. Thanks as always to my amazing sponsors.\nSee you next month.\n."},{"id":"content:blog:5.2023-june.md","path":"/blog/2023-june","dir":"blog","title":"June 2023: Flores, Australia and preparing for major releases","description":"Traveling to Flores, Indonesia and coming back home to Australia while preparing for major releases.","keywords":["Personal Updates","Work Updates","Numbers / Financials","Final thoughts"],"body":" Personal Updates Flores We spent the last week of our South East Asia trip in Flores, an island in Indonesia, east of Bali. Flores is best known for its Komodo dragons and the glowing lakes of Mt Kelimutu volcano. We did a road trip from East (Maumere) to West (Labuan Bajo), weaving along \"spaghetti roads\" through the mountains and\ndense jungle. We passed tiny villages as we went that offered a unique glimpse into the life of the Floresian and the Manggarai people. Nearly everyone lived off the land, with rice being grown as the main source of food. Occasionally we'd see someone roasting their coffee beans which I also had a go at. When inspecting the jungle, we'd always see something growing:\nmangoes, bananas, passion-fruit, cocoa, vanilla pods, taro, etc. Our highlights were: Kelimutu volcano, which has 3 crater lakes at the top. These lakes change colour over time, and we were lucky enough to see them in their blue, green and black state. Rinca Island, seeing the Komodo dragons in their natural habitat. Bena Traditional Village, walking amongst amazing thatched roof houses. Australia: A wedding and camping After Flores, we flew back to Australia for a wedding. It's winter time here, so it was a bit of a shock to the system after 3 months of high heat and humidity. After the wedding, we took a trip to the Warrumbungles with some friends. The Warrumbungles is a national park north-east of Sydney which is best known for its amazing night sky views. We camped there for a few days, doing some hikes and exploring the area. Afterwards we spent time in the Blue Mountains and Sydney catching up with family and friends,\nand planning the Europe leg of our trip. Work Updates June was an important month of work,\ngetting things ready for the next major releases of my Nuxt modules. The main blocker was Nuxt OG Image which had continued to plague me with issues. With some luck and many hours of work, I was able to get it to a state where it's mostly working correctly on all\nmajor runtimes and was able to unblock myself. This was a huge relief, and I'm looking forward to pushing out all Nuxt SEO modules in the coming months. Numbers / Financials ⭐ 6112 GitHub stars (+705) Open-Source ⌛ 87 hrs (+3 hrs) 💸 $867 AUD (+$123) $10.36 AUD / hour (+$1.51 / hour) Main projects Nuxt Modules Audit (13.5 hours) Some preliminary work to audit the state of Nuxt modules and authors. Still need some time to finish this off. Nuxt SEO Modules (45 hours) Fixing outstanding issues to unblock work on the new majors deveolopment. Mostly involved\nNuxt OG Image, Nuxt Simple Sitemap and Nuxt Simple Robots. Work also going into the initial versions of the new Nuxt Site Config module. 6 hours Unlighthouse Adding new CSV reporting feature and fixing some minor bugs. Freelancing ⌛ 10.25 hrs (+1.5 hrs) 💸 $1,025 AUD $100.00 AUD / hour (-24.24 / hour) Small bit of adhoc bug fixing work for one of my clients. Fun with AWS SNS notifications... Final thoughts It's really nice ce to be back in Australia and to have some more time to dedicate to work.\nThere are exciting things planned for July, some have already been released, and there's much more to come. Thanks as always to my amazing sponsors.\nSee you next month!"},{"id":"content:blog:6.2023-july.md","path":"/blog/2023-july","dir":"blog","title":"July 2023: Nuxt Release Week, Turning 29 and Exploring Greece","description":"What I got up to in July in my open-source work and my travels.","keywords":["Open-source Metrics","What I Got Up To","Other Small Wins","August Plan"],"body":" Open-source Metrics What I Got Up To Nuxt Release Week While working and travelling it's difficult to build momentum. I have a constant pulse of things requiring attention: GitHub issues,\n Discord support questions, client issues etc. I like helping people, it's rewarding in of-itself. But going for long periods\nwithout releasing new projects is demotivating. That's why I was excited for the start of July. I had an entire week free in Sydney to focus on shipping. With this excitement, I naively announced that I'd get something out every day on Twitter. The v2 release of Nuxt SEO Kit has been something bouncing around in my mind for many months, so I decided to focus on that. It started off really fun with overwhelmingly positive feedback. I even received my first Pizza sponsorship.\nAn extra special shout out to Omar McPizza Each night I was staying up progressively later and later. By the Friday release at 7am, I was done. I had burnt the candle at both ends and was ready to enjoy the weekend. I wasn't able to get Nuxt SEO Kit v2 out, but I was happy with what I had achieved. A month of work finished in one week. Even though it was a grind, it was really fun, and I'm grateful to the community\nfor their support. Turning 29 On the 10th July, my girlfriend and I flew to Greece, starting the Europe leg of our travels. This also happened to be one of my more unique birthdays. I spent the entire day in airplanes and airports, ending\nwith a premium sleep on the floor of Singapore airport. Being 28 was a good time with many memories and accomplishments while still trying to find my feet in open-source. I'm especially proud of joining team Nuxt, and I'm excited for the year ahead and have plenty of exciting projects to release Exploring Greece We got to Athens just as they were in a heat wave, going from the Australian winter to 40 degree heat. Our plan was to get to Istanbul, exploring what we could on the way, with an obligatory Greek island stop. We had a lot of fun in Athens, especially at the archaeological sites and museums. This was also our introduction to authentic Greek food, which has become our new favourite cuisine. After Athens, we travelled to the beautiful Greek island Naxos. We really liked exploring Halki village, the Old Town and beaches. After Naxos, we headed inland to Meteora, which is known for its stunning rocky outcrops and monasteries. It was quite impressive. Struggling with the heat, we decided to chill in Thessanoliki for a few days. The food, bars and museums were top tier. Our final stop in Greece was the charming town of Xanthi, where we spent time wandering the picturesque cobblestone streets. Other Small Wins VueFes Japan : I'll be speaking at VueFes Japan on the 28th October, would love to see you there! Nuxt Scripts : Started working on Nuxt Script in collaboration with the Google Aurora team Nuxt Link Checker : Released v2 with Live Inspections. August Plan Release Nuxt SEO Kit v2 (hopefully) Nuxt Scripts private beta 🚌 Türkiye, Bulgaria, North Macedonia, Albania, Montenegro"},{"id":"content:blog:7.2023-august.md","path":"/blog/2023-august","dir":"blog","title":"August 2023: Türkiye, Bulgaria, North Macedonia and Albania","description":"What I got up to in August in my open-source work and my travels.","keywords":["Open-source","🇹🇷 Türkiye","🇧🇬 Bulgaria","🇲🇰 North Macedonia","🇦🇱 Albania","September Plan"],"body":" In August me and my girlfriend continued travelling, getting to 4 new countries: Türkiye, Bulgaria, North Macedonia and Albania. A typically morning was waking up and trying to piece together the details of where we even where, we were moving fast\nafter Greece. We did a quick detour in Türkiye, then the many cramped and sweaty bus getting around the Balkans. I was surprised to still manage to get 109 hours to work on open-source, but I was starting to feel the burnout of\nmanaging work and travel. Open-source The highlight of the month was getting out Unhead v1.3 which included\nsome significant performance improvements. I continued work on Nuxt Scripts, trying to figure out the scope of the project and how it would work. The rest of my time went to maintenance of Nuxt SEO . 🇹🇷 Türkiye Our first day in İstanbul was massive, exploring all the stunning Mosque's. You can easily hit up 5 mosques and only walked\na few kms. We stayed in a locals neighbourhood and hit up the family-run restaurants. I didn't know at that point, but I'd be eating\nsome form of \"kebab\" and drinking Turkish coffee every day for the next month. Safe to say, I also took advantage of the Turkish sweets. After being thoroughly mosque'd out, we headed to the famous \"balloon\" town Göreme within the Cappadocia region. Göreme is stunningly scenic little town that is an Instagram tourist haven and in turn is a bit of a tourist trap.\nBut we still enjoyed it! We did a full-day hike through Rose valley, coming across\nimpressively well-endowed rock formations, historical abandoned cave churches and a friendly dog\nwho guided us. This was one of my favourite hikes, we had the trail almost entirely to ourselves, it was scenic and exploring the caves with\nour new dog friend was fun. The balloons were expensive, so I decided to skip. But I did hear good things and the ground view\nitself was pretty impressive. 🇧🇬 Bulgaria Bulgaria was the start of\nthe Balkans for us, which meant I was in for a lot of cheese pies and more kebabs. We did a quick couple of days in Plovid, which had a nice old town and some interesting Roman ruins. Then we were\noff to the capital city Sofia. It had some interesting architecture and a nice park. From Sofia, we went to do Seven Rila Lakes hike, which was one of our highlights. The hike is a chair lift, followed by a 3-hour ascent to the peek. Along the way you'll see 7 lakes, each at a different altitude. It was a fun hike, everyone had brought their dogs along and had picnics at the lakes. You get to the peak, and it's a stunning view - if the clouds aren't blocking it. 🇲🇰 North Macedonia We spent a couple of days in Skopje, a city characterised by an excess of modern statues and monuments. Many of them\nartificially made to look historical. A nice day trip from Skopje is Matka Canyon, you can do some nice hiking, lake swims and kayaking. The rest of our time in North Macedonia was spent in Ohrid, a beautiful lake town. It seems to be the holiday destination\nof choice for anyone in the Balkans who doesn't have easy access to the coast. Every night we were there, there was a festival on the lake, with fireworks, music and rides. 🇦🇱 Albania We didn't know much about Albania beforehand, we had heard good things from other travellers though and we were excited.\nThey told us about the friendly locals, and it was beautiful nature. Our first stop was Gjirokastër, Albania. Gjirokastër is a UNESCO world heritage site, with a classic old town and castle.\nWe got a good workout climbing up to the castle, which had some nice views. We also did a day trip to the Blue Eye, which is a natural spring that is a stunning blue colour. You can jump from\na few metres up into the water, which was fun. From there, we hit the coastal town of Himarë. Himarë is one of the remaining chill beach towns in the Balkans, that\nhasn't been completely overrun with tourism development. Our last stop for Albania was the Northern town of Shkodër. From Shkodër\nyou can go and stay in the Albanian Alp town of Theth. We spent 4 days in Theth, doing the Volbona pass hike and chasing waterfalls. It was really cosy and relaxing to be surrounded by\nthe mountains, it was a good recovery from the fast pace of the Balkans. September Plan 🚌 Montenegro, Bosnia & Herzegovina, Croatia, Slovenia, Hungary, Czechia"},{"id":"content:blog:how-the-heck-does-vite-work.md","path":"/blog/how-the-heck-does-vite-work","dir":"blog","title":"How Does Vite Work - A Comparison to webpack","description":"A deep-dive into the comparisons between the earliest Vite version and webpack. Discover what I learnt digging into internals and how I, correctly, guessed Vite was the next big thing.","keywords":["A Recap on Vite","Vite vs webpack","Understanding webpack","Understanding Vite","Production Builds","Summary","Getting started with Vite","Thanks for reading"],"body":" Note: This article was written for the alpha of Vite. I'd recommend reading Patak's great article on The Vite Ecosystem . In rebuilding my old Nuxt.js personal site, I wanted to challenge myself to learn the latest tech, the unknown. The unknown was the new project by Evan You: ⚡ Vite (/veet/). Called Fast, for the 🇫🇷 Frenchies. I'll be comparing how Vite works to the standard webpack config using webpack-dev-server , which all major Vue frameworks\nare using. We'll be looking at how Vite no-bundling works, by first looking at how webpack's bundling works and what the difference is. Afterwards I'll give you some\nrecommendations for setting up Vite for yourself. Vite could the next best thing in tooling, currently, it's still in a pre-release stage though so be careful out there 🐛. A Recap on Vite Vite is a web development build tool which supports Vue, React and Preact. It's an experimental new direction in how build tools can work with a greenfield ecosystem. Vite's core functionality is similar to webpack + webpack-dev-server with some core improvements\non developer experience: ⌛ Less time waiting for your app to start, regardless of app size 🔥 Hot module reloading (HMR) that is basically instant, regardless of app size 🔨 On-demand compilation 🙅♂️ Zero configuration for numerous pre-processors out of the box 📜 Esbuild powered typescript / jsx (super quick) Speed Example To give you a quick idea on how much faster it is, the below comparison is for Vue CLI which uses webpack. The bigger your app\nis the more noticeable the speed difference will be. \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n Build Time Dev Server Start Time Dev Page Load Time Vue CLI 5.14s 2568ms 320ms Vite 2.39s 232ms ️ 379ms New Vue 3 project / 10 components / no Babel / 2nd run, in development. Vite vs webpack The main functional difference you'll notice with Vite and your webpack app, is how code is served in development and which modules are supported. Don't worry if the below terms don't make sense to you, we'll be exploring them below. webpack (Nuxt.js / Vue CLI / etc) Supported Modules: ES Modules , CommonJS and AMD Modules Dev Server: Bundled modules served via webpack-dev-server using Express.js web server Production Build: webpack Vite Supported Modules: ES Modules Dev Server: Native-ES-Modules, served via Vite using a Koa web server Production build: Rollup Check out Mozilla's article on ES Modules if they're new to you. Understanding webpack To understand how Vite works, it's best to look at how webpack works first. Even with its popularly, understanding webpack can be intimidating, so I'll try to keep it simple. webpack is versatile in what you can do with it, but at its core, it will: Starting with an entry file, build a tree of your dependencies: all the imports, exports, requires from your code/files Transform / compile modules: think transpiling js for older browsers, turning SCSS into CSS Use algorithms to sort, rewrite and concatenate code Optimise webpack In Development Assuming you're using one of the main Vue frameworks, when you start your app in development, it is going to do a few things: Bundle all of your code Start the webpack-dev-server, the Express.js web server which will serve the bundled code Setup sockets which will handle the Hot Module Reloading As you may notice with your own apps, the bigger they grow, the longer you have to wait to start coding. Bundling in development is quicker because you don't need to do as much with the code, however,\nas your app grows, it will become painfully slow, especially on older machines. webpack Component Example I created a default Vue 3 Vue CLI project, which has an entry App.vue file using the HelloWorld.vue component.\nLet's see how this component gets to my browser. HelloWorld.vue component: < script >\n export default {\n props : {\n msg : String\n }\n }\n script >\n \n < template >\n < div class = \" hello \" >\n < h1 > {{ msg }} h1 >\n div >\n template >\n \n < style scoped >\n h1 {\n color : green ;\n }\n style >\n When I start my app and visit localhost I get the following HTML from the Express.js server. \n < html lang = \" en \" >\n < head >\n < meta charset = \" utf-8 \" >\n < meta http-equiv = \" X-UA-Compatible \" content = \" IE=edge \" >\n < meta name = \" viewport \" content = \" width=device-width,initial-scale=1.0 \" >\n head >\n < body >\n < div id = \" app \" > div >\n < script type = \" text/javascript \" src = \" /js/chunk-vendors.js \" > script >\n < script type = \" text/javascript \" src = \" /js/app.js \" > script >\n body >\n html >\n You'll notice we have 2 script files there: chunk-vendor.js and app.js . On inspecting them you'd see a lot of gibberish looking code.\nit helps to use the webpack-bundle-analyzer to see how it works visually. chunk-vendors.js These are third-party modules, usually coming from node_modules . The two main libraries in here are Vue itself and sockjs which is used for HMR. app.js This is all the code for my application. It contains components, assets, etc. You'll notice that for an SFC it splits\nit into multiple modules. Taking a quick look at the app.js file, we can find some of the HelloWorld component code. As you can see in the above image,\nall parts of the SFC are separate modules: the wrapper, CSS, template, js. The wrapper module is defining and importing the other models, some beautiful code. /***/ \" ./src/components/HelloWorld.vue \" :\n /*!***************************************!*\\\n !*** ./src/components/HelloWorld.vue ***!\n \\***************************************/\n /*! exports provided: default */\n /***/ ( function ( module , __webpack_exports__ , __webpack_require__ ) {\n \n \" use strict \" ;\n eval ( \" __webpack_require__.r(__webpack_exports__); \\n /* harmony import */ var _HelloWorld_vue_vue_type_template_id_469af010_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./HelloWorld.vue?vue&type=template&id=469af010&scoped=true */ \\\" ./src/components/HelloWorld.vue?vue&type=template&id=469af010&scoped=true \\\" ); \\n /* harmony import */ var _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./HelloWorld.vue?vue&type=script&lang=js */ \\\" ./src/components/HelloWorld.vue?vue&type=script&lang=js \\\" ); \\n /* empty/unused harmony star reexport *//* harmony import */ var _HelloWorld_vue_vue_type_style_index_0_id_469af010_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./HelloWorld.vue?vue&type=style&index=0&id=469af010&scoped=true&lang=css */ \\\" ./src/components/HelloWorld.vue?vue&type=style&index=0&id=469af010&scoped=true&lang=css \\\" ); \\n\\n\\n\\n\\n\\n _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ].render = _HelloWorld_vue_vue_type_template_id_469af010_scoped_true__WEBPACK_IMPORTED_MODULE_0__[ \\\" render \\\" ] \\n _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ].__scopeId = \\\" data-v-469af010 \\\"\\n /* hot reload */ \\n if (true) { \\n _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ].__hmrId = \\\" 469af010 \\\"\\n const api = __VUE_HMR_RUNTIME__ \\n module.hot.accept() \\n if (!api.createRecord('469af010', _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ])) { \\n api.reload('469af010', _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ]) \\n } \\n \\n module.hot.accept(/*! ./HelloWorld.vue?vue&type=template&id=469af010&scoped=true */ \\\" ./src/components/HelloWorld.vue?vue&type=template&id=469af010&scoped=true \\\" , function(__WEBPACK_OUTDATED_DEPENDENCIES__) { /* harmony import */ _HelloWorld_vue_vue_type_template_id_469af010_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./HelloWorld.vue?vue&type=template&id=469af010&scoped=true */ \\\" ./src/components/HelloWorld.vue?vue&type=template&id=469af010&scoped=true \\\" ); \\n (() => { \\n api.rerender('469af010', _HelloWorld_vue_vue_type_template_id_469af010_scoped_true__WEBPACK_IMPORTED_MODULE_0__[ \\\" render \\\" ]) \\n })(__WEBPACK_OUTDATED_DEPENDENCIES__); }.bind(this)) \\n\\n } \\n\\n _HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ].__file = \\\" src/components/HelloWorld.vue \\\"\\n\\n /* harmony default export */ __webpack_exports__[ \\\" default \\\" ] = (_HelloWorld_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[ \\\" default \\\" ]); \" );\n The main takeaway here is that within the app.js file contains all modules for my app. webpack does let you chunk the bundles how you like, for Nuxt.js it chunks routes individually. The more chunks though,\nthe more requests and more potential blocking js. You may see the problem here, we have multiple monolith files that need to be generated anytime we want to use our app.\nWhen we change a file for HMR, we need to regenerate the entire file. Understanding Vite Vite doesn't set out to be a new bundler. Rather, it's a pre-configured build environment using the Rollup\nbundler and a tool for local development. Vite In Development Vite makes the assumption that developers are going to be using the latest browser versions, so it can safely rely on the\nlatest JS functionality straight from the browser - in other words, no babel transpiling! When you start Vite for the first time pre-optimisations will be done on your node_modules , then Koa ,\na light-weight node web server starts to serve your app. There is no bundling or compiling needed to start the dev server, so it's damn quick (< 300ms). When you open your Vite app you'll be served the index.html from the server. The browser is going to read the index.html \nand know how to parse the Native-ES-Module code. < script type = \" module \" > import \" /vite/client \" \n < div id = \" app \" > div >\n < script type = \" module \" src = \" /@app/index.js \" > script >\n Parsing the Native-ES-Module means it will read the export and import lines from your code. It will convert those\nlines into HTTP requests back to the server, where it will again read the export and import lines and make new requests. It will keep going through like this with your dependencies recursively, in a waterfall process, until everything has been resolved. Vite Component Example Let's take a look at how these requests are working in the browser. After I open my app at http://localhost:3000 , the browser has fetched the following index.js file from the web server: import ' /@theme/styles/main.scss?import ' ;\n import Layout from ' /@theme/Layout.vue ' ;\n import NotFound from ' /@theme/NotFound.vue ' ;\n import CardPost from ' /@theme/components/CardPost.vue ' ;\n \n const theme = {\n Layout,\n NotFound,\n enhanceApp ({ app , }) {\n app. component ( ' CardPost ' , CardPost)\n }\n };\n export default theme;\n Normally, in webpack, you would have to transpile this code to something legacy browsers can understand. Newer browsers know what to do with it, see es6 module dynamic import . Let's drill into that highlighted line which is requesting the CardPost SFC. The browser will turn that import into a request for http://localhost:3000/@theme/components/CardPost.vue . < script >\n import posts from ' ../../posts '\n \n export default {\n props : {\n postIndex : {\n type : Number,\n required : true ,\n }\n },\n computed : {\n post () {\n return posts[ this .postIndex]\n }\n }\n }\n script >\n \n < template >\n < div class = \" card-post \" >\n ...\n div >\n template >\n \n < style lang = \" scss \" scoped >\n .card-post {\n ...\n }\n style >\n Once the web server gets this request, it will need to compile the CardPost.vue file to javascript and send it back. Vite has many\noptimisations around the Vue compiling so this takes no time. Let's see what comes through: import posts from ' /.vitepress/posts.ts '\n \n import ' /@theme/components/CardPost.vue?type=style&index=0 '\n import { render as __render } from ' /@theme/components/CardPost.vue?type=template '\n \n const __script = {\n props : {\n postIndex : {\n type : Number,\n required : true ,\n }\n },\n computed : {\n post () {\n return posts[ this .postIndex]\n }\n }\n }\n __script.__scopeId = ' data-v-287b4794 '\n __script.render = __render\n __script.__hmrId = ' /@theme/components/CardPost.vue '\n typeof __VUE_HMR_RUNTIME__ !== ' undefined ' && __VUE_HMR_RUNTIME__. createRecord (__script.__hmrId, __script)\n __script.__file = ' /home/harlan/sites/new.harlanzw.com/app/.vitepress/theme/components/CardPost.vue '\n export default __script\n Cool, so quite a bit going on here. The main thing to note here is how it's split up the SFC into different modules which\nwill need separate requests to fetch. It hasn't bundled these imports into the SFC or some other monolith file. Dependencies: /.vitepress/posts.ts Template: /@theme/components/CardPost.vue?type=template Stylesheet: /@theme/components/CardPost.vue?type=style&index=0 If you're curious, this is what the style component response looks like, some nifty for sure. import { updateStyle } from ' /vite/client '\n \n const css = ' .card-post[data-v-287b4794] { \\n position: relative; \\n } \\n .card-post .prose[data-v-287b4794] { \\n max-width: 100% !important; \\n } \\n .card-post__link[data-v-287b4794] { \\n position: absolute; \\n left: 0; \\n top: 0; \\n width: 100%; \\n height: 100%; \\n content: \" \"; \\n z-index: 1; \\n } \\n .card-post__content[data-v-287b4794] { \\n background-color: white; \\n z-index: 1; \\n } \\n .card-post__effect[data-v-287b4794] { \\n z-index: -1; \\n content: \" \"; \\n height: 30px; \\n width: 100%; \\n position: absolute; \\n background-color: #059669; \\n transition: 0.2s; \\n opacity: 0; \\n top: 30px; \\n } \\n .card-post:hover .card-post__effect[data-v-287b4794] { \\n top: -5px; \\n opacity: 1; \\n transform: rotate(0.25deg); \\n } '\n updateStyle ( ' 287b4794-0 ' , css)\n export default css\n You can see how the above allows the Hot Module Replacement to work efficiently. When you have a module that is changed,\nsay the styles within a component, instead of reloading the entire component tree, only the style module needs to be replaced. You can also imagine with the above, where Vite slows down. Imagine hundreds of HTTP requests which rely on nested HTTP requests, recursively.\nFortunately, there are optimisation to avoid this situation after the first load. The server will return a 304\nUnmodified HTTP Status code for modules which haven't changed, meaning they will use the browser's cached version of the file. Vite scales well for any app size because it only needs to request the modules for the route you're on. Production Builds Since Vite is using Rollup, pre-configured, you'd expect a similar output from Vite as webpack. Vite does boast a quicker\nbuilder and potentially a smaller artifact size, as Rollup is a more efficient bundler than webpack. The main gotcha is that Vite can still only support ES Modules in the production build, meaning you can't have any dependencies\nwhich don't have ES Module exports. Vite is also pre-configured to handle your build as a universal app. A universal app is built using a client (virtual browser)\nand a server (node). Allowing it to pre-render the HTML pages, so robot crawlers can fetch your page content without executing\njs and speeding up the initial load for users. That means SEO friendly static sites out of the box 🎉. Summary While I haven't touched on a lot of the complexities of Vite and webpack, I've tried to show you the main difference, how\nbundling and no-bundling look in action. Hopefully you've seen why Vite is promising alternative. There is so much potential in the ecosystem at the moment, watch this space, given 12-months we could see an explosion of Vite related projects. If you want to find out more about Vite, I'd watch Evan's talk on Vite & VitePress . Getting started with Vite I'd recommend just spinning up bare-bones Vite to get a feel for it. It's really easy, takes less than a minute. npm init vite-app\n Once you are sold, it's worth checking out the ecosystem before you build. Recommendations You shouldn't be looking to replace Vue CLI or webpack with Vite for existing projects yet, but it may be valuable to check out for new smaller scoped projects. The Vite ecosystem isn't that mature yet, the two main projects I'd recommend checking out are VitePress and Vitesse . If you are in need of a documentation site then VitePress is awesome, you can follow the VuePress documentation to fill in any gaps. VitePress abstracts away\nthe Vite configuration, which will be limiting for non-documentation sites. Otherwise, I'd choose Vitesse as it's going to give you more flexible on customising your app. Vitesse offers a pre-configured vite.config.js , so you can easily\nstrip anything out you don't need to add whatever you'd like to it. If you like my blog (VitePress + TailwindCSS), then you're more than welcome to clone it . Thanks for reading If you like the technical side of Vue and Laravel, I'll be posting regular articles on this site. The best\nway to keep up to date is by following me @harlan_zw . html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}"},{"id":"content:blog:modern-package-development.md","path":"/blog/modern-package-development","dir":"blog","title":"Learning Modern Package Development: Monorepos and Backends - Part 1","description":"Discover how you can build a modern package development through how I created the Unlighthouse project.","keywords":["Introduction","Background: Why build Unlighthouse?","Deciding on the stack","Monorepo","Server","Client","Putting It Together - Part 2","Conclusion"],"body":" This article is a work in progress. Feel free to read it, but some sections are incomplete. Introduction Unlighthouse is an open-source package I built to scan your entire site using Google Lighthouse. Building it was chaotic, with day-long bugs, constant refactoring and endless documentation reading. Through building it, I learnt modern development practices, making use of the vast ecosystem of packages and tools. Background: Why build Unlighthouse? As a freelancer I keep on top of my clients organic growth with Google Search Console. Was a day like any other, looking at one of my clients' dashboard. Seemingly out of nowhere, I saw the trend of page position, clicks and page views in free fall. My clients' income was based on organic traffic, not good. Isolating the reason for the falling page rank wasn't easy. The site had issues, but what was causing the free fall? There was no easy way to know. To diagnose the issue, I used Google Lighthouse. I went through all pages of the site, noticing quite a number of issues. I spent a couple of days\nfixing them all up and improving the general performance of the site. What happened next? Things started turning around. I was able to invert the graph. Organic growth doubled in the next few months. Happy client. Now that was out of the way, how could I make it easier to stay on top of the health of the sites I manage? Deciding on the stack I needed to build a tool that would run Google Lighthouse on an entire site with just the home page URL. I had a plan of attack for the build. The backend would be build using Typescript and Node. The frontend client would be built using Vue and Vite. But how would I be able to design this in a way that was easy to build and maintain? I had seen the amazing work coming out of the UnJS ecosystem and knew that they could solve some of my problems. With that, the package would be known as Un (inspired by Unjs) Lighthouse . Keeping a keen eye on other modern packages coming out, I took some of the best practices and tools I saw implemented. The stack was split into three core parts: Monorepo: containing the dependencies to build, test and deploy the code Frontend: displaying searchable, filtering and sortable results Backend: generating the frontend, running the scans and providing an API for the frontend Monorepo Implementing a monorepo is a keystone for a large project which will ship multiple packages. It allows you to\ngroup up logic, dependencies and documentation into a single repository. PNPM PNPM is the new kid on the block of node package managers and has gained a large following quickly, for good reason. It is the most performant package manager and has first class support for monorepos. There are many benefits to using a monorepo for a package. My personal favourite is it allows me to easily isolate logic and dependencies for your package, letting you write simpler code. Allowing end users to pull any specific part of your package that they want to use. Vitest Vitest is also the new kid on the block of testing. It's original aim was to be a testing framework specifically for Vite, but it has ended up being a possible replacement for Jest entirely. Vitest makes writing your logic and tests a breeze and I'd recommend checking it out for any project. Unbuild This package is described as a \"A unified javascript build system\". In reality, it's a minimal config way to build your package code to ESM and CJS. One of the amazing features of unbuild is stubbing. This allows you can run source code from your dist folder, meaning it transpiles just-in-time. This allows you to completely cut out the build step when you're iterating and testing integrations on your package. It's as simple as unbuild --stub . import { defineBuildConfig } from ' unbuild '\n \n export default defineBuildConfig ({\n entries : [\n { input : ' src/index ' },\n { input : ' src/process ' , outDir : ' dist/process ' , builder : ' mkdist ' , declaration : false },\n ],\n })\n GitHub Repo Link Server Lighthouse Binary Unlighthouse wouldn't be possible if Google hadn't published Lighthouse as its own NPM binary . To make Unlighthouse fast, I combined the binary with the package puppeteer-cluster , which allows for multi-threaded lighthouse scans. Unctx It's amazing that a simple pattern like composition has evaded Node packages for so long. With the introduction of Vue 3, composition became cool. And with that, unctx is composition for your own package. unctx allows you to define a scope where there's only a single instance of something that is globally accessible. This is incredibly useful for building packages, as you no longer need to be juggling core state. You can build your logic out as composables that interact with the core. import { createContext } from ' unctx '\n \n const engineContext = createContext < UnlighthouseContext >()\n \n export const useUnlighthouse = engineContext.use as () => UnlighthouseContext\n \n export async function createUnlighthouse ( userConfig : UserConfig , provider ?: Provider ) {\n // ...\n engineContext. set (ctx, true )\n }\n unctx GitHub Repo Hookable For Nuxt.js users, you might be familiar with the concept of frameworks hooks. A way for you to modify or do something with the internal logic of Nuxt. Building a package, I knew that this was a useful feature, not just for end-users, but for me as a way to organise logic. Having a core which is hookable means you can avoid baking logic in that may be better suited elsewhere. For example, I wanted to make sure that Unlighthouse didn't start for integrations until they visited the page. I simply set a hook for it to start only when they visit the client. hooks. hookOnce ( ' visited-client ' , () => {\n ctx. start ()\n })\n Hookable GitHub Repo Unconfig Unconfig is a universal solution for loading configurations. This let me allow the package to load in a configuration from unlighthouse.config.ts or a custom path, with barely any code. import { loadConfig } from ' unconfig '\n \n const configDefinition = await loadConfig < UserConfig >({\n cwd : userConfig.root,\n sources : [\n {\n files : [\n ' unlighthouse.config ' ,\n // may provide the config file as an argument\n ... (userConfig.configFile ? [userConfig.configFile] : []),\n ],\n // default extensions\n extensions : [ ' ts ' , ' js ' ],\n },\n ],\n })\n if (configDefinition.sources?.[ 0 ]) {\n configFile = configDefinition.sources[ 0 ]\n userConfig = defu (configDefinition.config, userConfig)\n }\n Unconfig GitHub Repo ufo Dealing with URLs in Node isn't nice. For Unlighthouse I needed to deal with many URLS, I needed to make sure they were standardised no matter how they were formed. This meant using the ufo package heavily. The slash trimming came in handy and the origin detection. export const trimSlashes = ( s : string ) => withoutLeadingSlash ( withoutTrailingSlash (s))\n const site = new $URL (url).origin\n ufo GitHub Repo Unrouted I needed an API for the client to communicate with the Node server to fetch the status of the scan and submit re-scans. The current JS offerings were a bit lackluster. I wanted something that just worked and had a nice way to use it. I ended up building unrouted as a way to solve that. group ( ' /api ' , () => {\n group ( ' /reports ' , () => {\n post ( ' /rescan ' , () => {\n // ...\n return true\n })\n \n post ( ' /:id/rescan ' , () => {\n const report = useReport ()\n const { worker } = useUnlighthouse ()\n \n if (report)\n worker. requeueReport (report)\n })\n })\n \n get ( ' __launch ' , () => {\n const { file } = useQuery <{ file : string }>()\n if ( ! file) {\n setStatusCode ( 400 )\n return false\n }\n const path = file. replace (resolvedConfig.root, '' )\n const resolved = join (resolvedConfig.root, path)\n logger. info ( `Launching file in editor: \\`${ path }\\` ` )\n launch (resolved)\n })\n \n get ( ' ws ' , req => ws. serve (req))\n \n get ( ' reports ' , () => {\n const { worker } = useUnlighthouse ()\n \n return worker. reports (). filter ( r => r.tasks.inspectHtmlTask === ' completed ' )\n })\n \n get ( ' scan-meta ' , () => createScanMeta ())\n })\n Unrouted GitHub Repo Client The code that what went into building the package. Vue 3 / Vite client The beloved Vite was to be used to make the development of the client as easy and fast as possible. Vue v3 used to make use of the vast collection of utilities available at VueUse . Putting It Together - Part 2 Part 2 of this article will be coming soon where I go over some technical feats in putting together the above packages. Conclusion Thanks for reading Part 1. I hope you at least found it interesting or some of the links useful. html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}"},{"id":"content:blog:nuxt-3-migration-cheatsheet.md","path":"/blog/nuxt-3-migration-cheatsheet","dir":"blog","title":"Nuxt 3 Migration Simplified: A Cheat Sheet","description":"A simplified cheat sheet for migrating from Nuxt 2 to Nuxt 3. Includes a list of all the changes you need to make to your Nuxt 2 project to get it working with Nuxt 3.","keywords":["Introduction","Resources","Pre-migration","Migration","Post Migration","Conclusion"],"body":" Introduction Upgrading to Nuxt 3 from Nuxt 2 can be a little intimidating. There are a ton of changes to keep track of, and it's easy to feel like you're lost in a maze of code. But don't worry, I'm here to help! I've started this cheat sheet\nto simplify the upgrade process for myself and for others who are brave enough to tackle it. It's a work in progress, and not intended to be exhaustive. I'll be updating it as I migrate more sites. Just remember that the migration process will take some time, patience, and persistence, depending on the size of your project and your prior experience. But hey, at least you'll learn a bunch. And if you're just looking to upgrade some modules, I've written an upgrade guide for 23 of them. Let's do this! Resources Links Docs Nuxt 3 - Docs Nuxt 3 - Official Migration Guide Nuxt 3 - Migration guide discussion Vue 3 - Migration Guide Tech UnJS – A collection of packages which make up the Nuxt core. VueUse – Essential Vue Composition API utilities. Vite.js and Rollup – Backbone of Nuxt 3 bundling. Nuxt 3 Examples Nuxt Examples - Minimal scoped examples NuxtBnB - Complex app Nuxt Movies - Complex app harlanzw.com - @nuxt/content app Education Free: NuxtNation 2022 YouTube playlist Paid: Mastering Nuxt Getting help Discord : create a post in #nuxt3-help if you get stuck Twitter Nuxt Community Post on StackOverflow with the tag Nuxt , kissu may help you if you're lucky Reddit Nuxt.js Need quicker help? Try providing a reproduction repo .\n \nAt a minimal, provide the output of npx nuxi info . Pre-migration Before you start upgrading, it's important to familiarise yourself with the new features and changes in Nuxt 3 and prepare your\nproject for the upgrade. 1. Get familiar with Nuxt 3 Learn the key differences between Nuxt 2 and Nuxt 3 and the new technologies that power it. Nuxt 3 Architecture While there are many similarities to Nuxt 2, the underlying architecture is completely new, more complex and more powerful. Start by learning the differences between: Nuxt (core context) Nuxt App (app context) Nitro (server engine) Nuxi (command line interface) You may want to re-familiarise yourself with the Rendering modes (SSR, SPA, Static). Daniel Roe's talk What happens when you start Nuxt 3 is a great place to start. Read the official migration guide Having a rough understanding of the migration process will allow you\navoid common pitfalls. Keep this open as you'll reference this as you continue on with the migration process. Play with Nuxt 3 Create a new fresh Nuxt 3 project using nuxt.new , play around with it. This will help you get a feel for the new features and how they work. Learn Vue 3 / Composition API Vue 3 is a major upgrade from Vue 2. You will need to learn the new syntax and features to be able to use Nuxt 3, particularly\nthe Composition API . Learn the basics of TypeScript Whether you like TypeScript or not is irrelevant. Nuxt 3 is written in TypeScript, trying to avoid it will only make your life harder. You don't need to be an expert, but you should be able to read and understand the Nuxt 3 code. You can learn the basics with TypeScript in 5 minutes . Learn about modules: CJS and ESM Nuxt 3 requires ECMAScript modules, so you'll need to convert your CommonJS modules. See the Nuxt ES modules page. Many of the issues you'll come across are due to using dependencies which are CJS.\nSo understanding the difference between them can be critical. Node modules can be a challenging subject, if you need further guidance see this article . 2. Prepare your Nuxt 2 app Before you start migrating, you should prepare your Nuxt 2 app to make the upgrade process easier. Upgrade Nuxt 2 If you are on an earlier version, the first step is upgrading. The latest Nuxt 2 uses Vue 2.7 , which supports the Composition API. npm i nuxt@^2.15.4\n # yarn add nuxt@^2.15.4\n Upgrade Modules Upgrade your modules to the latest version that supports Nuxt 2. This will make the upgrade process easier. Be careful not to upgrade modules which may only support Nuxt 3 in future versions. Enable component auto-imports Nuxt 3 has auto-imports enabled by default, if you're not using this feature in Nuxt 2, you should enable it to identify\nany component paths that need to be updated. All manual component imports should be removed. See Components Discovery for more information. export default {\n components : true\n }\n Audit your dependencies Having hard dependencies in your project that aren't compatible with Nuxt 3 / Vue 3 is going to massively slow you down. You should go through all your node dependencies, checking the following: Vue plugin: Supports Vue 3 Nuxt module: Supports Nuxt 3 or has an upgrade path. See Migration - 2. modules and the Nuxt 3 modules . Other: Scan with bundlephobia to find ESM support / alternatives. You can use CJS dependencies, but you'll need to transpile them. This is a good opportunity\nto drop any dependencies you don't need, migrate to dependencies which have better maintenance and / or are smaller. Migration This cheat sheet requires a fresh Nuxt 3 project and migrating things over one by one. In each step of migrating, you should test to see if everything is working.\nIf there are bugs, then you will be able to debug them easily. 1. Create the new app The first step is creating the new apps boilerplate and migrate over safe config from nuxt.config.ts . Create a Fresh Nuxt 3 App You can do so with npx nuxi init my-app . This will create a new Nuxt 3 project. Create Boilerplate By default, Nuxt 3 does not provide page routing and layouts. To make the migration easier, you'll be modifying the files to be Nuxt 2 compatible. To enable them we update app.vue with NuxtLayout and NuxtPage . < template >\n < div >\n < NuxtLayout >\n < NuxtPage />\n NuxtLayout >\n div >\n template >\n Create an empty default layout file that will be updated later. < template >\n < div >\n < slot / >\n div >\n template >\n Create a basic index page, so we can see our changes. < template >\n < div >\n < h1 >hello world h1 >\n div >\n template >\n Migrate Runtime Config / env Follow the Migrate Runtime Config doc. The env key in nuxt.config.ts is no longer supported, you should use runtime config exclusively. // Nuxt 2\n export default {\n privateRuntimeConfig : {\n apiKey : process.env. NUXT_API_KEY || ' super-secret-key '\n },\n publicRuntimeConfig : {\n websiteURL : ' https://public-data.com '\n },\n env : {\n NUXT_API_KEY : process.env. NUXT_API_KEY\n }\n }\n // Nuxt 3\n export default defineNuxtConfig ({\n runtimeConfig : {\n apiKey : process.env. NUXT_API_KEY || ' super-secret-key ' ,\n // Warning: `public` is exposed on client and server\n public : {\n websiteURL : ' https://public-data.com '\n }\n }\n })\n You can safely ignore TypeScript issues this causes for now. Migrate head If you previously had head configuration in your nuxt.config.ts , you should migrate it to app.head config. If you're using hid in your head config, unless you have a good reason, you should remove it. Read @vueuse/head v1 release notes \nto learn more. Note: Tags with href and src will not resolve relative links / aliases.\nYou will need to reference absolute links from the public directory. If you'd like to use aliases, you can use the nuxt-unhead module. // nuxt 2\n head: {\n meta: [\n { hid : ' description ' , name : ' description ' , content : ' My custom description ' },\n ]\n }\n // nuxt 3\n app: {\n head: {\n meta: [\n { name : ' description ' , content : ' My custom description ' }\n ]\n }\n }\n Copy Static files In Nuxt 3 the /static folder has been renamed to /public .\nMake the public directory and copy all of these files over. Copy CSS / Assets In Nuxt 3, the assets folder serves the same function.\nSimply copy over your assets' folder. Once done, update the css field. css: [\n ' ~/assets/main.css ' ,\n ]\n Most pre-processors will work with no extra config. You will just need to install the dependencies. For example, if you see an error like: Preprocessor dependency \" sass \" not found. Did you install it?\n You will need to run npm install -D sass . If you're having issues with linked images within CSS,\nyou may need to use absolute links to the images in the public directory. /* Nuxt 2 - we have the file: assets/images/logo.png */\n body {\n background-image : url ( ' ~/assets/images/logo.png ' );\n }\n /* Nuxt 3 - we have the file: public/images/logo.png */\n body {\n background-image : url ( ' /images/logo.png ' );\n }\n You may have some node_module dependencies in your css entries, install each of these as needed. 2. Modules Module migration can be a challenging part of the migration process. You will need to migrate each module one by one and test your app after each module. Nuxt 2 modules are not compatible with Nuxt 3 out of the box, so many modules are not supported yet. I have tried to provide an\nupgrade path for the most popular modules which are not supported. Copy over modules In Nuxt 3 all modules should belong under the modules key. You will need to copy over your modules from the buildModules and modules fields. export default defineNuxtConfig ({\n modules : [\n // your modules\n ]\n })\n Remove redundant modules The following modules can be safely removed as functionality they provide is redundant. @nuxtjs/typescript @nuxtjs/typescript-runtime @nuxtjs/composition-api @nuxtjs/dotenv nuxt-build-optimisations / nuxt-webpack-optimisations Remove Modules No Longer Recommended @nuxtjs/axios and @nuxt/http are not recommended with Nuxt 3. It's best practice to use the $fetch / useFetch API. If are determined to use $http you can use nuxt-alt/http . See the Nuxt 3 and Axios discussion for additional context. Use alternative modules @nuxtjs/auth Official @nuxt/auth support is a work-in-progress . In the meantime, you can use: sidebase/nuxt-auth nuxt-alt/auth @nuxtjs/supabase @nuxtjs/pwa The community nuxt-pwa-module module seems to be the best option. A core module for PWA seems to be undecided, if it goes ahead there's no ETA. @nuxtjs/i18n Official support is in beta, check the docs . @nuxtjs/proxy This feature is coming to nitro route rules. You can track it here: https://github.com/unjs/nitro/issues/113 . In the meantime, you can use the nuxt-alt/proxy module. @nuxtjs/eslint-module The Nuxt 2 version of this module is called @nuxtjs/eslint-config , you should remove this. Instead, follow the documentation for @nuxt/eslint-config . @nuxtjs/sitemap Not currently supporting Nuxt 3. Instead, you can use: nuxt-simple-sitemap or nuxt-seo-kit . @funken-studio/sitemap-nuxt-3 @nuxtjs/robots Not currently supporting Nuxt 3. Instead, you can use: nuxt-simple-robots or nuxt-seo-kit . Alternatively, create your own robots.txt file at public/robots.txt . User-agent: *\n Disallow: /\n OR you can generate robots dynamically. export default defineEventHandler (\n e => `User-agent: * \\n Disallow: / \\n Sitemap: https://YOUR_SITE/sitemap_index.xml`\n )\n @nuxtjs/vuetify Official support is coming soon. You can use nuxt-alt/vuetify in the meantime. @nuxtjs/device Not supporting Nuxt 3. Create your own plugin to detect the device instead, you can use the ua-parser-js package directly. import UAParser from ' ua-parser-js '\n \n export function useDevice () {\n const parser = new UAParser ()\n const { os, browser, device } = parser. getResult ()\n \n return {\n os,\n browser,\n device,\n isMobile : device.type === ' mobile ' ,\n isDesktop : device.type === ' desktop ' ,\n isTablet : device.type === ' tablet ' ,\n // etc\n }\n }\n < script lang = \" ts \" setup >\n const { isDesktop, browser } = useDevice ()\n script >\n @nuxtjs/moment Not supporting Nuxt 3. Create your own plugin to use Moment instead. import moment from ' moment '\n import ' moment/locale/en '\n \n export default defineNuxtPlugin (( nuxtApp ) => {\n moment. locale ( ' en ' )\n moment.tz. setDefault ( ' UTC ' )\n return {\n provide : {\n moment,\n }\n }\n })\n < script setup lang = \" ts \" >\n // access moment via $moment\n const { $moment } = useNuxtApp ()\n script >\n @nuxtjs/fontawesome I'd recommend using nuxt-icon . Alternatively,\nsee the Using FontAwesome in Nuxt 3 discussion for plugin code sample. @nuxtjs/dayjs See Nuxt3 support for plugin code sample. @nuxtjs/toast See Examples of using the @nuxtjs/toast or any vue toast plugin? discussion for plugin code sample. @nuxtjs/gtm You can create your own code to handle gtm. export default defineNuxtConfig ({\n app : {\n head : {\n script : [\n {\n innerHTML : `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\n new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\n j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n })(window,document,'script','dataLayer','YOUR_GTM_ID');`\n },\n ],\n },\n }\n })\n bootstrap-vue See nuxt3 starter? issue for code sample. Work in progress. @nuxtjs/google-analytics See Vue Plugins from the docs for an example of how to use Google Analytics with Nuxt 3. Alternatively,\nsee this comment \non how you can implement it with cookie control. Upgrade supported modules If the module you're using supports Nuxt 3, it's important to check the migration notes.\nIt's likely that the upgrade will include some\nbreaking changes. 3. State management If you're using Vuex 3, you will need to upgrade your state management, as Vue 3 does not support it. Consider if you need a state management library Nuxt 3 ships with its own statement management with the useState composable. If you're using a state management library, you should consider if you need it. Additionally, if you are dealing with data client side, you can always reference global refs. This isn't recommended for\nserver-side rendering as state is shared between users. import { ref } from ' vue '\n \n // You can import this counter from any file and the data will stay in sync\n export const counter = ref ( 0 )\n \n export function useCounter () {\n return {\n counter,\n increment () {\n counter.value ++\n },\n decrement () {\n counter.value --\n }\n }\n }\n Otherwise, migrate to Pinia or Vuex 4 Pinia is the recommended state management solution for Nuxt 3, but you can use Vuex 4 if you prefer. 4. Components Components will only be parsed if they are imported. This means it's safe to copy over all our components and mixin files from Nuxt 2. Copy components The folders have the same name and will be auto-imported. You don't need to add anything to nuxt.config.ts . If your components aren't being found,\nit may be because component auto-imports are done with path-prefixing by default,\nsee Component Names . Follow Component Options Migration See the Component Options Migration guide for more information. 5. Pages and dependencies You will be copying over your pages one by one and their dependencies. As you copy each page, you will run into errors. These errors may be related to breaking changes between Nuxt 2 and 3 or to missing dependencies. For example, you'll have missing layout, plugin and potentially component dependencies. It's important you reference the Nuxt 3 Migration Guide as you go.\nRemember to ask for help in the community channels if you get stuck. Home Page Start with the home page, pages/index.vue .\nThis will give you some momentum and help you get started. Copy static route pages You'll start with routes which aren't dynamic which tend to be less complex and easier. For example: pages/about.vue , pages/contact.vue , etc. Copy dynamic route pages Nuxt 3 uses a new file system routing system, which means that all our pages need to be converted to the new format. See Dynamic Routes for more information. 6. Server Routes / Middleware Nuxt 3 uses a completely new server engine and HTTP framework.\nThis means that all our server routes and middleware need to be converted to the new format. Migrate server routes See Migrate Server Routes . You'll likely need to completely rewrite your server routes. Migrate server middleware See Migrate Server Routes . You'll likely need to completely rewrite your server middleware. Post Migration 1. Recommendations Migrate to Composition API While this is not required, it is highly recommended.\nThe Composition API is a much better way to write Vue code and Nuxt provides\nbetter support for it. Tips: vue-composition-convertor may give you a head start ChatGPT is pretty good at converting Vue 2 options API to Vue 3 composition API Migrate everything to TypeScript Nuxt 3 is TypeScript first, it is the best practice to have all of your code in TypeScript. If you're using the Options API, you can simply add lang=\"ts\" to your Vue SFCs. Otherwise, if you are using the options API, you\ncan make use of the defineNuxtComponent function. < script lang = \" ts \" >\n export default defineNuxtComponent ({\n props : {\n name : {\n type : String,\n required : true\n }\n },\n mounted () {\n console. log ( this .name)\n }\n })\n script >\n Load static assets from an absolute path In Nuxt 2 it was common to load everything from ~/assets , however with Nuxt 3 the default is to use absolute paths. This way the bundler does not need to parse them. This will save you time when migrating and improve your build performance. Simply move images which aren't going to change to static and load them from / instead of ~/assets . Use Vue Macros To make the migration from Vue 2 as simple as possible, you can make use of vue-macros . It provides a number of useful build-time macros, which speeds up the time to convert the code. < script setup lang = \" ts \" >\n const { modelValue, count } = defineModel <{\n modelValue : string\n count : number\n }>()\n \n console. log (modelValue.value)\n modelValue.value = ' newValue '\n script >\n Switch to UnJS / VueUse where appropriate The VueUse and UnJS packages all support Nuxt 2 and Nuxt 3. If you can migrate to using them over other dependencies,\nyou'll save yourself a lot of time. 2. Testing Testing in Nuxt 3 is still under development. There is some documentation around it, but it is fairly minimal. Migrate to Vitest Nuxt 3 uses Vitest for testing. If you're using Jest or Mocha, you'll need to migrate. See the Why Vitest guide for more information. Use @nuxt/test-utils Follow the documentation on the Testing page, to get started. Add vitest-environment-nuxt (optional) The vitest-environment-nuxt package will likely take over as the official testing environment for Nuxt 3. It's still a work-in-progress with minimal documentation, but you may consider using it. Conclusion You likely still have some migration pain ahead of you, but you're well on your way, congratulations. Feel free to reach out to me directly on Twitter or Discord with any feedback or questions you may have. Keep in mind\nthat this migration guide does not intend to be exhaustive and is being actively updated. This post took me many hours to put together. Please consider sponsoring me if this has helped you. html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}"},{"id":"content:blog:nuxt3-hydration-node-mismatch.md","path":"/blog/nuxt3-hydration-node-mismatch","dir":"blog","title":"Nuxt 3 \"Hydration Mismatch\" Errors","description":"Learn about the causes and solutions for the Hydration Children Mismatch error in Nuxt 3, including the use of client-only components, the