<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Peter Mekhaeil</title><description>I am an experienced web developer with a focus on optimizing performance and delivering a seamless user experience. I teach web development using the latest technologies.</description><link>https://petermekhaeil.com/</link><item><title>3 benefits interviews bring you</title><link>https://petermekhaeil.com/3-benefits-interviews-bring-you/</link><guid isPermaLink="true">https://petermekhaeil.com/3-benefits-interviews-bring-you/</guid><description>Not only do interviews open doors to new opportunities, they also help you sharpen your skills with new learnings. Interviews keep your career moving forward and I will share some learnings from experience.</description><pubDate>Tue, 14 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Not only do interviews open doors to new opportunities, they also help you sharpen your skills with new learnings. Interviews keep your career moving forward and I will share some learnings from experience.&lt;/p&gt;
&lt;h2&gt;Learn about what you know&lt;/h2&gt;
&lt;p&gt;When you answer questions during the interview, be aware to what level you understand the topics being discussed. Speaking out loud about what you know helps you articulate those past career learnings and experiences. With each interview, the more you can improve your confidence in talking about what you know.&lt;/p&gt;
&lt;h2&gt;Learn about what you do not know yet&lt;/h2&gt;
&lt;p&gt;You may not have an answer to some questions during the interview and that is totally fine - this will be an opportunity to learn something new! Ask the interviewer for the answers to those questions. Now whether or not you decide to continue with the interview rounds, you have now learnt something new that you can take to your existing job or you take to your next interview. With each interview, you are building up your knowledge base.&lt;/p&gt;
&lt;h2&gt;Learn to negotiate&lt;/h2&gt;
&lt;p&gt;When you get to the offer stage of the interviews, you have the opportunity to practice negotiation, a very handy skill to have at any level in your career. With each interview, sharpen your negotiation skills and pick up experience in handling those situations.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A great book on this topic is &lt;a href=&quot;https://learninpublic.org/&quot;&gt;The Coding Career Handbook&lt;/a&gt; - a chapter is dedicated to salary negotiation where you’ll learn that you have an upper hand simply because the best alternative is staying at the job you already have.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Each experience will be unique&lt;/h2&gt;
&lt;p&gt;Each person will have their own learning experience. With each interview, practice a useful skill for your career, such as building your confidence, expanding your knowledge and salary negotiating.&lt;/p&gt;
</content:encoded></item><item><title>Accelerate your delivery with modern tools</title><link>https://petermekhaeil.com/accelerate-your-delivery-with-modern-tools/</link><guid isPermaLink="true">https://petermekhaeil.com/accelerate-your-delivery-with-modern-tools/</guid><description>Learn about how to use modern tooling to speed up your development, benefiting both the business and the development team.</description><pubDate>Tue, 27 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By using modern tools in the development of large front-end repositories, we can potentially improve the speed of deliverables. This will benefit both the business and the development team. The business will benefit from a faster time to market, and the development team will be more motivated as a faster pipeline reduces the need for context switching. I&apos;ll share 3 development tools that will lead to faster delivery of your development.&lt;/p&gt;
&lt;h3&gt;Use a fast package manager&lt;/h3&gt;
&lt;p&gt;A fast package manager will improve developer productivity by reducing the time it takes to install packages and manage dependencies. This will lead to reducing build times and faster feature delivery.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pnpm.io/&quot;&gt;pnpm&lt;/a&gt; is described as a fast, disk space-efficient package manager. It focuses on &lt;a href=&quot;https://pnpm.io/motivation&quot;&gt;saving disk space while boosting installation speed&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;pnpm is performant because it uses a &lt;a href=&quot;https://pnpm.io/symlinked-node-modules-structure&quot;&gt;content-addressable store&lt;/a&gt; that contains each version of each dependency and pnpm will hard link the project&apos;s &lt;code&gt;node_modules&lt;/code&gt; to that store. This means that pnpm only stores one copy of each package in the store, regardless of how many projects are using it. This strategy will save disk space and improve the speed of installing and managing packages.&lt;/p&gt;
&lt;p&gt;I&apos;ve seen this result in projects speeding up their installations by &lt;strong&gt;75%&lt;/strong&gt;. The team behind pnpm have also posted &lt;a href=&quot;https://pnpm.io/benchmarks&quot;&gt;benchmarks&lt;/a&gt; on how pnpm compares to other package managers in terms of installation speed.&lt;/p&gt;
&lt;h3&gt;Migrate to a fast development tool&lt;/h3&gt;
&lt;p&gt;A fast development tool can reduce the time it takes to build and deploy. This can be particularly important for large applications that is frequently deployed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; is a modern JavaScript development tool that focuses on providing a fast and efficient development experience. It uses a new approach to building and serving web applications using native browser capabilities to load modules on demand, providing a faster and more efficient development experience.&lt;/p&gt;
&lt;p&gt;One of the advantages Vite provides is how incredibly fast it is at building. Vite uses rollup for bundling and &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt; for transforming. Esbuild is written in Go and is 10-100x faster than the JavaScript-based alternatives.&lt;/p&gt;
&lt;p&gt;Vite includes &lt;a href=&quot;https://vitejs.dev/guide/build.html#library-mode&quot;&gt;Library Mode&lt;/a&gt; that allows you to bundle libraries that do not have an HTML entry. Not only can you use Vite to build your applications, you can also take advantage of the power of Vite for packages that are consumed by those applications.&lt;/p&gt;
&lt;p&gt;Swapping out a create-react-app (CRA) app to Vite is a matter of replacing the npm scripts and reconfiguring &lt;code&gt;vite.config.js&lt;/code&gt; to match your needs. I&apos;ve seen real world CRA apps migrate to Vite and have improved their build times by &lt;strong&gt;50%&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Use a build system&lt;/h3&gt;
&lt;p&gt;Build systems like &lt;a href=&quot;https://turbo.build/repo&quot;&gt;Turborepo&lt;/a&gt; and &lt;a href=&quot;https://nx.dev/&quot;&gt;Nx&lt;/a&gt; can cache the results of tasks to speed up the build process in subsequent builds.&lt;/p&gt;
&lt;p&gt;Each task in a CI pipeline has inputs and outputs - for example, a build task may have source code as inputs and bundled files as outputs. When a task is run, the build system generates a hash based on the inputs and stores the output under that hash. If the task is run again and the inputs have not changed, the build system can reuse the saved output stored under the same hash, rather than running the task again.&lt;/p&gt;
&lt;p&gt;This can be especially helpful for large projects with many tasks, as it reduces the overhead of running tasks that have not changed, and can significantly reduce CI time.&lt;/p&gt;
</content:encoded></item><item><title>AI Native Documentation</title><link>https://petermekhaeil.com/ai-native-documentation/</link><guid isPermaLink="true">https://petermekhaeil.com/ai-native-documentation/</guid><description>Documentation teams are adapting to AI crawlers. Here are techniques they&apos;re using to make their docs AI native.</description><pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most documentation traffic now comes from AI crawlers and agents read docs more than humans do. Here&apos;s how teams are adapting.&lt;/p&gt;
&lt;h2&gt;Render Markdown for Agents&lt;/h2&gt;
&lt;p&gt;Agents send &lt;code&gt;Accept: text/markdown&lt;/code&gt; in the request header when fetching pages. Servers can detect this and return markdown instead of HTML. Less tokens, cleaner content.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/bunjavascript/status/1971934734940098971&quot;&gt;Bun&lt;/a&gt; and &lt;a href=&quot;https://x.com/thdxr/status/1972421466953273392&quot;&gt;@thdxr&lt;/a&gt; shared this pattern.&lt;/p&gt;
&lt;h2&gt;AI Prompts&lt;/h2&gt;
&lt;p&gt;Docs now include AI prompts you can paste directly into your agent to handle setup.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://clerk.com/docs/nextjs/getting-started/quickstart&quot;&gt;Clerk Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/&quot;&gt;Sentry Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.com/docs/guides/getting-started/ai-prompts&quot;&gt;Supabase Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ask AI&lt;/h2&gt;
&lt;p&gt;Docs sites embed AI chat as a sidebar or popup. Many are powered by &lt;a href=&quot;https://www.kapa.ai/&quot;&gt;Kapa&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cursor.com/docs&quot;&gt;Cursor Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.prisma.io/docs&quot;&gt;Prisma Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/docs&quot;&gt;Vercel Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Copy Page as Markdown&lt;/h2&gt;
&lt;p&gt;Make it one click to copy documentation as markdown.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cursor.com/docs&quot;&gt;Cursor&lt;/a&gt; and &lt;a href=&quot;https://vercel.com/docs&quot;&gt;Vercel&lt;/a&gt; do this with a &quot;Copy page&quot; link in the sidebar.&lt;/p&gt;
&lt;h2&gt;Markdown URL Support&lt;/h2&gt;
&lt;p&gt;Support viewing pages as Markdown by adding &lt;code&gt;.md&lt;/code&gt; to the end of the URL. &lt;a href=&quot;https://nuqs.dev/docs&quot;&gt;nuqs&lt;/a&gt; does this and includes a &quot;Copy Markdown URL&quot; button along with &quot;Open in...&quot; options to open the markdown URL directly in ChatGPT or Claude.&lt;/p&gt;
&lt;h2&gt;llms.txt&lt;/h2&gt;
&lt;p&gt;Put a &lt;a href=&quot;https://llmstxt.org/&quot;&gt;llms.txt&lt;/a&gt; in the root. Some sites include links to key docs, others include the full content in markdown format.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/llms.txt&quot;&gt;nextjs.org/docs/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/llms.txt&quot;&gt;tanstack.com/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ui.shadcn.com/llms.txt&quot;&gt;ui.shadcn.com/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some sites provide both &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt; files.&lt;/p&gt;
&lt;h2&gt;MCP Servers&lt;/h2&gt;
&lt;p&gt;Projects run their own MCP servers for documentation, like &lt;a href=&quot;https://docs.astro.build/en/guides/build-with-ai/#astro-docs-mcp-server&quot;&gt;Astro Docs MCP Server&lt;/a&gt;. &lt;a href=&quot;https://context7.com/&quot;&gt;Context7&lt;/a&gt; works across documentation for many tools and libraries.&lt;/p&gt;
</content:encoded></item><item><title>Be strong like your code</title><link>https://petermekhaeil.com/be-strong-like-your-code/</link><guid isPermaLink="true">https://petermekhaeil.com/be-strong-like-your-code/</guid><description>Staying fit and active is important to our well-being and I&apos;d like to remind us to take care of ourselves while we code away.</description><pubDate>Sat, 21 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We spend our days building scalable and performant applications, so why are we not doing the same to our mind and body? Staying fit and active is important to our well-being and I&apos;d like to remind us to take care of ourselves while we code away.&lt;/p&gt;
&lt;h3&gt;Daily Maintenance&lt;/h3&gt;
&lt;h4&gt;H20 yourself&lt;/h4&gt;
&lt;p&gt;Water is your body&apos;s battery to keep you going throughout day. I remind myself to drink water by keeping the water bottle near my monitor. Keep it in view so you take regular sips while working.&lt;/p&gt;
&lt;p&gt;Get yourself 2L of water a day.&lt;/p&gt;
&lt;h4&gt;Take breaks&lt;/h4&gt;
&lt;p&gt;We get reminded of this so often but we ignore it because we tend to be in the &quot;zone&quot;. Take your breaks. It will help you stay focus in the long run.&lt;/p&gt;
&lt;p&gt;Don&apos;t spend more than 15min on a problem. Take a break, leave it for the next day, or ask someone to help. If you have spend more than that on a problem, chances are you have gone down the wrong path. We have all been there - we spend hours on a problem, and after sleeping it off, we find a solution first thing in the morning because we started over.&lt;/p&gt;
&lt;h4&gt;Take the stairs&lt;/h4&gt;
&lt;p&gt;Aim for 10 sets of stairs a day. This will also help achieve the recommended 10,000 steps a day. I know this is easier said than done. Start with one set, build a habit. You&apos;ll start doing 10 naturally without too much thought.&lt;/p&gt;
&lt;h4&gt;Stand while working&lt;/h4&gt;
&lt;p&gt;Prolonged hours sitting down has a hugh impact on your posture and health. Get yourself a standing desk, or ask your office to supply one for you. Stand up while taking a call. Even yet, go for a walk while talking. Not only will it help you with your health, it will help shorten the duration of the call.&lt;/p&gt;
&lt;h4&gt;Cut out the snacks&lt;/h4&gt;
&lt;p&gt;Very common I see engineering desks packed with towers of snacks and sugars (&lt;em&gt;I&apos;m so guilty of supplying these snacks!&lt;/em&gt;) to keep engineers going for long hours. I&apos;ve learnt to avoid this at all costs. Replace with fruits or nuts. The sugar hit is only temporary and fil the stomach for a short time, but it has a long term effect on your health that is not worth it.&lt;/p&gt;
&lt;h4&gt;Avoid desk lunches&lt;/h4&gt;
&lt;p&gt;Lunch is your break away from work, take advantage of it. Go for a walk, stretch your legs and have lunch outside. Get some sunlight and walk off your meal afterwards. Otherwise you&apos;ll get distracted and get back to work, or even be interrupted by a colleague.&lt;/p&gt;
&lt;h3&gt;Working Out&lt;/h3&gt;
&lt;h4&gt;Get your blood flowing&lt;/h4&gt;
&lt;p&gt;Cannot stress this one enough. I&apos;ve picked up many injuries because of my laziness to warm up before a work out. While at work, 3pm would hit and I would close my laptop. I then rush to the office gym, change into my active wear and get straight on the treadmill without warming up. You can imagine what happened next. Because we have been sitting down for long hours coding, our muscles are not prepared for the surprise they face when we rush to the gym. This is where stretching is important and I talk about it later below.&lt;/p&gt;
&lt;h4&gt;Create a workout schedule&lt;/h4&gt;
&lt;p&gt;It not too important how much you workout, as long as you are consistent with your schedule. Book your week in advance and block out your calendar so you are not interrupted by work activities. Add some buffer for warming up and stretching. I suggest having a training program that you can follow.&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;Recommendation: &lt;a href=&quot;http://teamgritocr.com/&quot;&gt;Team Grit OCR&lt;/a&gt; have a 4-week home workout suitable for all fitness levels. Does not require equipment and can be done at home.&lt;/em&gt;)&lt;/p&gt;
&lt;h4&gt;Join a Club&lt;/h4&gt;
&lt;p&gt;Think of it as pair programming. Find other like-minded engineers who want to join the fun and stay active together by joining a club. For your colleagues, you can start a private group chat to share your schedules or tips. It&apos;s easier to start an activity with others. And it goes two ways - let them know you&apos;re willing to keep them company in the gym.&lt;/p&gt;
&lt;p&gt;Connect with them on &lt;a href=&quot;https://strava.com/&quot;&gt;Strava&lt;/a&gt; to track and stay in touch with each other.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Strava turns every iPhone and Android into a sophisticated running and cycling computer (and we work with your GPS watches and head units, too). Start Strava before an activity and you can track your favorite performance stats, and afterwards, dive deep into your data. - &lt;a href=&quot;https://strava.com/&quot;&gt;Strava.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(&lt;em&gt;&lt;a href=&quot;https://www.strava.com/athletes/60074731&quot;&gt;Connect with me on Strava&lt;/a&gt; - I would love to virtually run with other engineers!&lt;/em&gt;)&lt;/p&gt;
&lt;h4&gt;Measure your activities&lt;/h4&gt;
&lt;p&gt;Like we execute performance tests against our applications, we should also be measuring our physical activities and use the results as benchmarks. Track your activities in your favourite notes editor, or use an app to automatically track the activities for you. I personally use Notes on my iPhone + &lt;a href=&quot;https://connect.garmin.com/&quot;&gt;Garmin Connect&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Stretching&lt;/h3&gt;
&lt;p&gt;Prior to knowing the importance of stretching, I would spend a lot of long hours on my desk coding away and over time, picked up poor posture which led to a few nasty injuries in my shoulders and hips.&lt;/p&gt;
&lt;p&gt;Common scenarios I see engineers (&lt;em&gt;and myself!&lt;/em&gt;) faced without awareness:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hunched Shoulders&lt;/strong&gt; - Because the desk and chair are not aligned, so the shoulders become tensed for a long period of time while typing at the wrong height.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tensed Hips&lt;/strong&gt; Caused by long hours sitting down, the hips don&apos;t get any movement. Over time, the muscles tighten up and will cause injury when you need to participate in an activity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some stretches I have picked over time to help with common tightness from an extended period of sitting down in front of a computer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://yogapose.com/pose/childs-pose&quot;&gt;Child&apos;s Pose&lt;/a&gt; to release tensions on your back.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://orthoinfo.aaos.org/en/recovery/rotator-cuff-and-shoulder-conditioning-program&quot;&gt;External Shoulder Rotations&lt;/a&gt; to stretch out your shoulders.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=RInK0asX8l0&quot;&gt;Place a tennis ball under your hamstrings&lt;/a&gt; while sitting down to help release tightness while working.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without going into too much detail on how to stretch - My message here is to bring awareness of the importance of stretching for anyone who is sitting down for a prolonged period of time.&lt;/p&gt;
&lt;p&gt;(&lt;em&gt;Recommendation: Living in Singapore? Visit &lt;a href=&quot;https://www.thestretchclinic.com/sg/&quot;&gt;The Stretch Clinic&lt;/a&gt;. I regularly visit them for a good stretch and they have been very helpful with taking care of my body from any injuries I have faced from long hours at work or while working out.&lt;/em&gt;)&lt;/p&gt;
</content:encoded></item><item><title>Building a Today I Learned Habit</title><link>https://petermekhaeil.com/building-a-til-habit/</link><guid isPermaLink="true">https://petermekhaeil.com/building-a-til-habit/</guid><description>Discover how maintaining a Today I Learned (TIL) list can foster personal growth, collaboration, and lifelong learning. Learn how to start your own TIL and explore its benefits.</description><pubDate>Sat, 28 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the best habits I&apos;ve picked up is maintaining a &quot;Today I Learned&quot; (TIL) list. What started as a casual way to jot down things I didn&apos;t want to forget has grown into a useful way to track what I&apos;m learning.&lt;/p&gt;
&lt;h2&gt;Why TIL?&lt;/h2&gt;
&lt;p&gt;It all started with the realization that I was learning something new every day, but often these lessons would fade as quickly as they came. Whether it was solving a coding problem, uncovering a technical insight during a discussion, or learning from a mistake, I wanted a way to capture these moments.&lt;/p&gt;
&lt;p&gt;So, I began documenting them. Over time, this habit evolved into my TIL list, a structured way to track and reflect on my growth.&lt;/p&gt;
&lt;h2&gt;Inspired by &quot;Learn in Public&quot;&lt;/h2&gt;
&lt;p&gt;The idea of TIL is closely tied to the concept of &lt;a href=&quot;https://www.swyx.io/learn-in-public&quot;&gt;Learn in Public&lt;/a&gt; popularized by Shawn Wang (aka Swyx). The principle is simple: share your learning journey openly so others can benefit from your experiences. This approach not only reinforces your own understanding but also invites collaboration and feedback from the community.&lt;/p&gt;
&lt;p&gt;By embracing &quot;Learn in Public&quot;, my TIL list became more than just a personal tool. It turned into a way to give back, spark discussions, and connect with others who share similar interests.&lt;/p&gt;
&lt;h2&gt;Turning a Habit into a System&lt;/h2&gt;
&lt;p&gt;Consistency is key to making any habit stick. To ensure I didn&apos;t lose momentum, I built a system around TIL:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Daily Capture&lt;/strong&gt;: Each day, I aim to record at least one key takeaway. It could come from debugging, reading, or collaborating with others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automating Reminders&lt;/strong&gt;: I set up tools like calendar alerts to remind me to add new entries. These small nudges keep me on track.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Periodic Reflection&lt;/strong&gt;: Regular reviews of my TIL entries help me reinforce past learnings and spot areas for deeper exploration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This structured approach has kept the practice alive and valuable.&lt;/p&gt;
&lt;h2&gt;Broadening the Scope&lt;/h2&gt;
&lt;p&gt;While my TILs are currently focused on technical insights, the beauty of this habit is its flexibility. It doesn&apos;t have to be limited to coding or development topics. You can capture learnings from any area like leadership lessons, communication strategies, process improvements, or even personal hobbies. The key is to document anything that adds value to your growth and understanding.&lt;/p&gt;
&lt;p&gt;If you&apos;re starting a TIL list, think broadly. Reflect on all aspects of your day-to-day experiences. Did you learn something new about managing a team? Did a conversation provide a fresh perspective? These moments are just as valuable as technical insights and deserve a place in your list.&lt;/p&gt;
&lt;h2&gt;How I organize mine&lt;/h2&gt;
&lt;p&gt;As your TIL list grows, having a structure can make it more useful. Here&apos;s an example of a format you might use:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2023-01-20&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/til/structured-clone/&quot;&gt;Deep clone object with structuredClone()&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-01-31&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/til/spaces-vs-tabs/&quot;&gt;Spaces vs tabs: It&apos;s an accessibility issue&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-02-28&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/til/git-multiple-accounts/&quot;&gt;Git: Work with multiple accounts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This format can help you quickly search and revisit specific learnings. You could also add categories like &quot;Technical&quot;, &quot;Leadership&quot; or &quot;Communication&quot; to make your list even more navigable.&lt;/p&gt;
&lt;h2&gt;Sharing and Collaborating&lt;/h2&gt;
&lt;p&gt;One of the most unexpected benefits of TIL has been its impact on collaboration. Sharing my TILs, whether in team meetings, blog posts, or discussions, has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sparked conversations and new ideas.&lt;/li&gt;
&lt;li&gt;Helped others solve similar problems.&lt;/li&gt;
&lt;li&gt;Encouraged teammates to start their own TIL lists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By &quot;Learning in Public&quot;, I&apos;ve seen how sharing knowledge can amplify its value.&lt;/p&gt;
&lt;h2&gt;Why You Should Start a TIL List&lt;/h2&gt;
&lt;p&gt;Whether you&apos;re a beginner or a seasoned expert, starting a TIL list can transform your approach to learning. Here&apos;s why:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build Confidence&lt;/strong&gt;: Seeing your list grow over time is a great reminder of how much you&apos;ve learned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Give Back&lt;/strong&gt;: Sharing your learnings online might help someone else solve a problem they&apos;re facing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay Curious&lt;/strong&gt;: TILs make you more attuned to new opportunities to learn.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you&apos;re new to the idea, start small. You don&apos;t need to publish your list right away. Even a private journal can work wonders. But if you&apos;re comfortable, take the plunge and share your learnings with the world. You might be surprised at the connections and conversations that follow.&lt;/p&gt;
&lt;p&gt;What did you learn today? Share it with me on &lt;a href=&quot;https://twitter.com/petermekh&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Building my personal AI Assistant</title><link>https://petermekhaeil.com/building-my-personal-ai-assistant/</link><guid isPermaLink="true">https://petermekhaeil.com/building-my-personal-ai-assistant/</guid><description>How I built a self-hosted AI assistant that connects to my daily life.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I built an AI assistant that runs on my homelab. I named her Tatl, after the fairy from Majora&apos;s Mask.&lt;/p&gt;
&lt;p&gt;In May I attended &lt;a href=&quot;https://www.ai.engineer/singapore&quot;&gt;AI Engineer Singapore&lt;/a&gt;, Singapore Foreign minister Vivian Balakrishnan gave a keynote about his personal AI setup, a self-hosted assistant running on a Raspberry Pi. It was his second brain. He shared the setup in a &lt;a href=&quot;https://gist.github.com/VivianBalakrishnan/a7d4eec3833baee4971a0ee54b08f322&quot;&gt;public gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It clicked so I had to build my own.&lt;/p&gt;
&lt;h2&gt;What Tatl Can Do&lt;/h2&gt;
&lt;p&gt;Tatl connects to the services I use daily:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gmail&lt;/strong&gt; - summarises yesterday&apos;s emails every morning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Calendar&lt;/strong&gt; - briefs me on today&apos;s agenda each morning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Garmin + Strava + Oura&lt;/strong&gt; - pull my fitness data and training plans on demand&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt; - check cameras&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Homelab&lt;/strong&gt; - monitors services like Uptime Kuma and Grafana, sends alerts when something is down&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Voice notes&lt;/strong&gt; - send a voice message and she transcribes it locally via &lt;a href=&quot;https://github.com/ggml-org/whisper.cpp&quot;&gt;whisper.cpp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The morning briefing means I can mute most of my notifications during the day.&lt;/p&gt;
&lt;p&gt;Every message is processed with persistent memory. She builds up a knowledge graph over time and recalls relevant context before responding.&lt;/p&gt;
&lt;h2&gt;Visualising the Wiki&lt;/h2&gt;
&lt;p&gt;The memory graph synthesises into markdown pages that Tatl writes and maintains. To browse them I use &lt;a href=&quot;https://quartz.jzhao.xyz/&quot;&gt;Quartz&lt;/a&gt; - a static site generator that turns markdown into an Obsidian-style website with graph view. Self-hosted privately.&lt;/p&gt;
&lt;h2&gt;The Stack&lt;/h2&gt;
&lt;p&gt;Built on &lt;a href=&quot;https://nanoclaw.dev/&quot;&gt;NanoClaw&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runs on the Mac Mini and handles message routing&lt;/li&gt;
&lt;li&gt;Each conversation spins up an isolated Docker container running Claude&lt;/li&gt;
&lt;li&gt;Credentials are managed through a local vault - containers never see API keys&lt;/li&gt;
&lt;li&gt;Memory is stored in a local knowledge graph via &lt;a href=&quot;https://github.com/mnemon-dev/mnemon&quot;&gt;mnemon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Voice notes transcribed locally via &lt;a href=&quot;https://github.com/ggml-org/whisper.cpp&quot;&gt;whisper.cpp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Wiki is visualised using &lt;a href=&quot;https://quartz.jzhao.xyz/&quot;&gt;Quartz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I learned&lt;/h2&gt;
&lt;p&gt;I tried local models first - Qwen, Mistral, Gemma. At the time, they weren&apos;t reliable enough for tool use. The ecosystem is moving fast though and I will revisit this.&lt;/p&gt;
&lt;p&gt;The memory layer is what makes it feel different. Without it, it&apos;s just a chatbot with integrations.&lt;/p&gt;
&lt;h2&gt;Get your own&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://nanoclaw.dev/&quot;&gt;NanoClaw&lt;/a&gt; is open source and runs on anything from a Raspberry Pi to a Mac Mini.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get a Claude API key&lt;/li&gt;
&lt;li&gt;Start with &lt;a href=&quot;https://nanoclaw.dev/skills/telegram&quot;&gt;Telegram&lt;/a&gt; as the main messaging channel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then explore from there, you&apos;ll find yourself adding more of your day-to-day to it naturally.&lt;/p&gt;
&lt;p&gt;The hardest part was finding a name for the agent.&lt;/p&gt;
</content:encoded></item><item><title>Page Speed Performance of Formula 1 Websites</title><link>https://petermekhaeil.com/f1-page-speed-insights/</link><guid isPermaLink="true">https://petermekhaeil.com/f1-page-speed-insights/</guid><description>Performance report using PageSpeed Insights and Chrome User Experience Report to showcase which Formula 1 team has the fastest web performance.</description><pubDate>Tue, 15 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;F1 2020 is over - Mercedes took 1st and 2nd position, with Red Bull right behind them in 3rd. But which team won in terms of web speed performance?&lt;/p&gt;
&lt;h3&gt;How Scores are Measured&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/about&quot;&gt;PageSpeed Insights&lt;/a&gt; is a tool that reports on site performance and it includes both lab data and real-world field data.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#lab_data&quot;&gt;Lab Data&lt;/a&gt; is collected by running &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; and is measured under controlled conditions - simulating a single device with fixed network conditions. &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#field_data&quot;&gt;Field Data&lt;/a&gt; is collected from &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report/&quot;&gt;Chrome User Experience Report (CrUX)&lt;/a&gt; and it is capturing real-world user experience - these are from real page views over a set period of time.&lt;/p&gt;
&lt;p&gt;I have aggregated the results and built a site to show us a summary: &lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We will start with the score everyone wants to see first:&lt;/p&gt;
&lt;h3&gt;Performance Score&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;At the time of this post, &lt;strong&gt;Haas&lt;/strong&gt; takes the lead with a score of 64. This score is a weighted average of a collection of &lt;a href=&quot;https://web.dev/performance-scoring/&quot;&gt;performance metrics&lt;/a&gt; measured in Lighthouse:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/first-contentful-paint/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; - time at which first content appears.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/speed-index/&quot;&gt;Speed Index&lt;/a&gt; - how quickly content visibly populates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; - time at which largest content appears.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/interactive/&quot;&gt;Time to Interactive&lt;/a&gt; - time at which page becomes interactive.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/lighthouse-total-blocking-time/&quot;&gt;Total Blocking Time&lt;/a&gt; - time from FCP to Time to Interactive.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt; - the movement of elements within the viewport.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Important to note here that these Lighthouse scores are based on &lt;strong&gt;Lab Data&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A score of 90 or above is considered good. So these teams have some work to do on their season breaks.&lt;/p&gt;
&lt;h3&gt;Field Data Scores&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The list is ordered according to their Performance Scores. We can see &lt;strong&gt;Racing Point&lt;/strong&gt; takes the lead for &lt;code&gt;Cumulative Layout Shift&lt;/code&gt; and &lt;strong&gt;Williams&lt;/strong&gt; for &lt;code&gt;Largest Contentful Paint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You will notice 3 of the Field Data metrics are used in the Performance Scores. The 4th one being &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;. Each of these metrics have their own &lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/about#categories&quot;&gt;scoring&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Team Breakdown&lt;/h3&gt;
&lt;h4&gt;Racing Point&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The big number is the performance score we spoke about earlier. Right under it is the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt; assessment summary.&lt;/p&gt;
&lt;p&gt;Core Web Vitals are a common set of signals critical to all web experiences, measuring the quality of a site&apos;s user experience. A site either passes, or does not pass the assessment. 3 metrics are used to measure a website&apos;s Core Web Vital assessment: &lt;code&gt;Largest Contentful Paint (LCP)&lt;/code&gt;, &lt;code&gt;Cumulative Layout Shift (CLS)&lt;/code&gt; and &lt;code&gt;First Input Delay (FID)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To pass the assessment, 75% of page views need to hit the recommended target of each metric for it to be considered good performance. When the 3 metrics hit that target, the site passes the assessment. Take a read on how Google &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;defines the metrics thresholds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the example above, Racing Point passed. And it can be seen in more details on the right hand side. These are the Field Data scores we saw earlier, but broken down to indicate the percentage of viewers and their experience. For example, for CLS - 79% of viewers had a good experience and 9% had a poor experience.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at the Williams too. They scored very similar scores but with a noticeable difference:&lt;/p&gt;
&lt;h4&gt;Williams&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The Lab Data didn&apos;t perform as well (which is totally fine! Lab Data has its purposes) and it passed the Core Vital assessment from the Field Data, collected from real-world users.&lt;/p&gt;
&lt;h3&gt;Time strips&lt;/h3&gt;
&lt;p&gt;Perhaps it is not always about the score? Take a look at the time strip Lighthouse has generated for both sites:&lt;/p&gt;
&lt;h4&gt;Racing Point Timestrip&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Williams Timestrip&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We can see that content is loaded earlier for Williams.&lt;/p&gt;
&lt;h3&gt;Read More&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://jakearchibald.com/2019/f1-perf/&quot;&gt;Jake Archbald&lt;/a&gt; has written a detailed post on who has the fastest website in F1. Highly recommend to read his break down analysis.&lt;/p&gt;
&lt;h3&gt;How does &apos;F1 Page Speed Insights&apos; site work?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt; was built using &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; - Using the &lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/get-started&quot;&gt;PageSpeed Insights API&lt;/a&gt; and the &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report/api/guides/getting-started&quot;&gt;Chrome UX Report API&lt;/a&gt; to collect the data. Both sets of data are collected during the build of the app and a static export is deployed on &lt;a href=&quot;http://netlify.app/&quot;&gt;Netlify&lt;/a&gt;. This site will refresh monthly for us to monitor changes to the data.&lt;/p&gt;
&lt;h3&gt;UppubDate: 2020-12-19&lt;/h3&gt;
&lt;p&gt;Previously on &lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt;, the performance score was based on &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#lab_data&quot;&gt;Lab Data&lt;/a&gt; captured from &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Lighthouse&lt;/a&gt;. Lab Data has it&apos;s benefits - the data is collected from a simulated load on a site using a single device and a fixed network connection. This allows for a reproducible environment which aids in debugging performance issues.&lt;/p&gt;
&lt;p&gt;Lighthouse scoring can &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/blob/master/docs/variability.md&quot;&gt;fluctuate&lt;/a&gt;. This is because of external conditions such as internet traffic routing, testing on difference devices or network conditions. And because of this, it might not be the right choice to the performance comparison I am looking for.&lt;/p&gt;
&lt;p&gt;I have come across &lt;a href=&quot;https://github.com/treosh/lighthouse-plugin-field-performance&quot;&gt;lighthouse-plugin-field-performance&lt;/a&gt; which lets Lighthouse weigh the scoring using the &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#field_data&quot;&gt;Field Data&lt;/a&gt; collected from the &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report/&quot;&gt;Chrome UX Report&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The scoring algorithm weighs values for Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS) and picks a minimum score. It uses Core Web Vitals assessment that expects all its metrics to pass thresholds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Updating the F1 Page Speed Insights scoring to follow this approach results in new positions in the scoring:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The scoring is based on the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt; metrics and Lighthouse will score those metrics accordingly. The metric with the lowest score is the final performance score.&lt;/p&gt;
&lt;p&gt;All of &lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt; data comes from the Chrome UX Report which is collected from real-world users over the last 28 days.&lt;/p&gt;
&lt;p&gt;Credits to &lt;a href=&quot;https://treo.sh/&quot;&gt;Treo&lt;/a&gt; for this approach!&lt;/p&gt;
</content:encoded></item><item><title>How I landed my first job as a software engineer</title><link>https://petermekhaeil.com/how-i-landed-my-first-job-as-a-software-engineer/</link><guid isPermaLink="true">https://petermekhaeil.com/how-i-landed-my-first-job-as-a-software-engineer/</guid><description>Landing your first engineering job is tough. I share my experience and the lesson I learnt that has helped throughout my career.</description><pubDate>Fri, 28 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was once an entry level developer with no experience, looking for my first door into an engineering career. I finished my studies back in 2009 and for almost a year I could not find a job. It almost felt like a blind journey from one interview to another, that all ended up in rejection because of lack of experience and knowledge. Without knowing where to look or without knowing what to research, I was mostly answering interview questions with answers that I thought was right, based on my limited knowledge in web development (my computer science studies did not include web dev subjects).&lt;/p&gt;
&lt;p&gt;It was until my approach changed during an interview at what soon to become the first company that accepted me.&lt;/p&gt;
&lt;p&gt;At the very start of the interview, without any thought behind it - I told the engineering manager something I’ve never said during my previous interviews: I said something along the lines of &lt;em&gt;“Before we continue, I want to tell you - I do not know anything. But I want to learn”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Everything changed from that moment. The manager continued the interview by listing their tech stack and asked if any of it sounds familiar, and my answer was no to all of them. Nevertheless, he did not mind because I showed my eagerness to learn. The interview turned into a crash course introduction to the technologies and soon after, I was accepted for the job.&lt;/p&gt;
&lt;p&gt;Call out your area of focus so others are aware that you are know. Shawn calls this &lt;a href=&quot;https://www.swyx.io/lampshading/&quot;&gt;the Power of Lampshading&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For entry level developers looking to land their first job as a software engineer, it is totally okay to call out your area of focus. Let the interviewers know that you do not know the answers and you are willing to learn. This is powerful in itself and calling out your focus areas will have an impact throughout your career.&lt;/p&gt;
</content:encoded></item><item><title>Data Fetching in Next.js</title><link>https://petermekhaeil.com/data-fetching-nextjs/</link><guid isPermaLink="true">https://petermekhaeil.com/data-fetching-nextjs/</guid><description>Next.js offers different methods to fetch data for your page content - choosing the right approach for your application will allow you to benefit from the performant advantages that Next.js provides.</description><pubDate>Tue, 18 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Next.js offers different methods to fetch data for your page content - choosing the right approach for your application will allow you to benefit from the performant advantages that Next.js provides. We will first start with describing the different methods available, followed with an e-commerce use-case example.&lt;/p&gt;
&lt;h3&gt;Static Site Generation vs Server-side Rendering&lt;/h3&gt;
&lt;h4&gt;Static Rendering&lt;/h4&gt;
&lt;p&gt;Your pages are generated as static HTML and this is done at compilation time. It is then served to your users when they request your page.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is done by using the &lt;code&gt;getStaticProps&lt;/code&gt; API. If Next.js finds a page using only this API to fetch data, it will pre-render the page at build time using the props returned from this API.&lt;/p&gt;
&lt;p&gt;The content can be cached and served from a CDN. The advantages of this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Very performant&lt;/strong&gt; - no server required to render content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always available&lt;/strong&gt; - no need for a database when user requests the page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less load on backend services&lt;/strong&gt; - the backend is only required during compilation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although, because the content is generated ahead of time, it can be outdated and requires additional deployments to refresh the content.&lt;/p&gt;
&lt;h4&gt;Server-side Rendering&lt;/h4&gt;
&lt;p&gt;When a user requests a page, Next.js will generate the content on the server and return it to the user. This is done on per request.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next.js will enable Server-side rendering when the &lt;code&gt;getServerSideProps&lt;/code&gt; API is used in a page.&lt;/p&gt;
&lt;p&gt;Additional deployments are not necessary and the user will always get the latest content because the server will be ready to take in requests and generate the content, but the page will lose the benefits of static site generation and it will have the added processing time required to build the page on the server.&lt;/p&gt;
&lt;h3&gt;Use Case: E-commerce&lt;/h3&gt;
&lt;p&gt;You can configure each page of your application to fetch data differently. Let&apos;s take an e-commerce app as an example:&lt;/p&gt;
&lt;h4&gt;Product Catalog Page: Static generation&lt;/h4&gt;
&lt;p&gt;A product listing page will require a list of all available products. We can use &lt;code&gt;getStaticProps&lt;/code&gt; to generate a static HTML page and Next.js can fetch the content from an external data source during build time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function getStaticProps() {
  return {
    props: {
      products: await getProducts()
    }
  };
}

const Products = ({ products }) =&amp;gt; {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Products&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {products.map((product) =&amp;gt; (
          &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/section&amp;gt;
  );
};

export default Products;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Product Detail Page: Static generation with dynamic routes&lt;/h4&gt;
&lt;p&gt;We need a page for each product and for us to do this, we will use &lt;code&gt;getStaticPaths&lt;/code&gt; to return a list of product IDs. For each product ID, we will generate a static product page:&lt;/p&gt;
&lt;p&gt;Create a page &lt;code&gt;page/products/[id].js&lt;/code&gt; which uses &lt;a href=&quot;https://nextjs.org/docs/routing/dynamic-routes&quot;&gt;dynamic routes&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Pre-render the path of each product
export async function getStaticPaths() {
  const products = await getProducts();

  const paths = products.map((product) =&amp;gt; ({
    params: { id: product.id }
  }));

  return { paths };
}

// Pre-render the page with data related to each product
export async function getStaticProps({ params }) {
  return {
    props: {
      product: await getProduct(params.id)
    }
  };
}

const Product = ({ product }) =&amp;gt; {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;{product.name}&amp;lt;/h1&amp;gt;
    &amp;lt;/section&amp;gt;
  );
};

export default Product;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Shopping Cart: Static generation + client-side fetching&lt;/h4&gt;
&lt;p&gt;The content on a shopping cart is different for each user, we can render the page layout as static content and then load the client-side data in the browser:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ShoppingCart = () =&amp;gt; {
  const { data } = useSWR(&apos;/api/cart&apos;, fetchCart);

  return (
    &amp;lt;section&amp;gt;
      &amp;lt;h1&amp;gt;Your Cart&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.products.map((product) =&amp;gt; (
          &amp;lt;li key={product.id}&amp;gt;{product.name}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/section&amp;gt;
  );
};

export default ShoppingCart;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example uses &lt;a href=&quot;https://swr.vercel.app/&quot;&gt;SWR&lt;/a&gt; to handle the data fetching.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Next.js supports different ways to fetch data, with static site generation having the advantages required for a fast page load experience. Take the load off the browser rendering your content and prepare it ahead of time during compilation time.&lt;/p&gt;
&lt;p&gt;This approach is demonstrated with projects such as &lt;a href=&quot;https://petermekhaeil.com/improving-shopify-page-performance-using-next.js/&quot;&gt;Go Bare&lt;/a&gt;, an e-commerce website that fetches the content from Shopify and builds the static HTML content. The user is viewing content served from a CDN, rather than requesting it directly from Shopify. When the user is at the shopping cart, we do a client side fetch to Shopify to request data related to the shopping cart session.&lt;/p&gt;
</content:encoded></item><item><title>How to build an npx starter template</title><link>https://petermekhaeil.com/how-to-build-a-npx-create-template-starter/</link><guid isPermaLink="true">https://petermekhaeil.com/how-to-build-a-npx-create-template-starter/</guid><description>Learn to build your own `npx create-my-template` script for your starter template.</description><pubDate>Mon, 05 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our favourite frameworks have starter templates to help us get started with setting up our projects with minimal configuration. Some known examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reactjs.org/docs/create-a-new-react-app.html#create-react-app&quot;&gt;Create React App&lt;/a&gt; (&lt;code&gt;npx create-react-app my-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/api-reference/create-next-app&quot;&gt;Next.js&lt;/a&gt; (&lt;code&gt;npx create-next-app@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remix.run/docs/en/v1#getting-started&quot;&gt;Remix&lt;/a&gt; (&lt;code&gt;npx create-remix@latest&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have created a &lt;a href=&quot;https://github.com/petermekhaeil/create-my-template&quot;&gt;sample starter template&lt;/a&gt; that I will walk through in this article.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How they work&lt;/strong&gt;: these npm packages have an executable file configured in the &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;create-my-template&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;bin&quot;: {
    &quot;create-my-template&quot;: &quot;./index.js&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the user installs the npm package, it will also install the executable configured in &lt;code&gt;bin&lt;/code&gt;, allowing the user to run the executable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install create-my-template
$ create-my-template
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npx&lt;/code&gt; is part of &lt;code&gt;npm&lt;/code&gt;: it allows you to run a command from an npm package. If the package has a single &lt;code&gt;bin&lt;/code&gt; script configured, that command will be used. &lt;code&gt;npx&lt;/code&gt; will also fetch the package remotely if it is not installed locally.&lt;/p&gt;
&lt;p&gt;This means users run the below command and &lt;code&gt;npx&lt;/code&gt; will download the package and run the executable in one line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npx create-my-template
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we can execute a command, let&apos;s write up an installation script that will generate a starter template. A basic script can look like the one below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node

// Usage: npx create-my-template my-app

const spawn = require(&apos;cross-spawn&apos;);
const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);

// The first argument will be the project name.
const projectName = process.argv[2];

// Create a project directory with the project name.
const currentDir = process.cwd();
const projectDir = path.resolve(currentDir, projectName);
fs.mkdirSync(projectDir, { recursive: true });

// A common approach to building a starter template is to
// create a `template` folder which will house the template
// and the files we want to create.
const templateDir = path.resolve(__dirname, &apos;template&apos;);
fs.cpSync(templateDir, projectDir, { recursive: true });

// It is good practice to have dotfiles stored in the
// template without the dot (so they do not get picked
// up by the starter template repository). We can rename
// the dotfiles after we have copied them over to the
// new project directory.
fs.renameSync(
  path.join(projectDir, &apos;gitignore&apos;),
  path.join(projectDir, &apos;.gitignore&apos;)
);

const projectPackageJson = require(path.join(projectDir, &apos;package.json&apos;));

// Update the project&apos;s package.json with the new project name
projectPackageJson.name = projectName;

fs.writeFileSync(
  path.join(projectDir, &apos;package.json&apos;),
  JSON.stringify(projectPackageJson, null, 2)
);

// Run `npm install` in the project directory to install
// the dependencies. We are using a third-party library
// called `cross-spawn` for cross-platform support.
// (Node has issues spawning child processes in Windows).
spawn.sync(&apos;npm&apos;, [&apos;install&apos;], { stdio: &apos;inherit&apos; });

console.log(&apos;Success! Your new project is ready.&apos;);
console.log(`Created ${projectName} at ${projectDir}`);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a basic example. Other things you will need to consider when you build your own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can use tools like &lt;a href=&quot;https://www.npmjs.com/package/commander&quot;&gt;commander&lt;/a&gt; or &lt;a href=&quot;https://www.npmjs.com/package/inquirer&quot;&gt;inquirer&lt;/a&gt; to add interactive command-line interface.&lt;/li&gt;
&lt;li&gt;Add some error-handling. Examples: Does the project directory already exists before attempting to create it? Did the user specify the project name?&lt;/li&gt;
&lt;li&gt;Check which package manager the user prefers to use (eg pnpm, yarn).&lt;/li&gt;
&lt;li&gt;Add some styling using &lt;a href=&quot;https://www.npmjs.com/package/chalk&quot;&gt;chalk&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Do you want to initialize a git repository in the project directory?&lt;/li&gt;
&lt;li&gt;Should the installation script do a clean up if installation failed?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Working on a monorepo?&lt;/strong&gt; This approach can also be used to create packages in your monorepo from a template!&lt;/p&gt;
&lt;p&gt;Once you&apos;re happy with your starter template, you can &lt;a href=&quot;https://docs.npmjs.com/cli/v6/commands/npm-publish&quot;&gt;npm publish&lt;/a&gt; for everyone to use.&lt;/p&gt;
&lt;h2&gt;Source code examples&lt;/h2&gt;
&lt;p&gt;A great way to learn is by reading the source code of other starter templates that are known across the community:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/withastro/astro/tree/main/packages/create-astro&quot;&gt;Astro&lt;/a&gt; (&lt;code&gt;npm create astro@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/create-react-app/tree/main/packages/create-react-app&quot;&gt;Create React App&lt;/a&gt; (&lt;code&gt;npx create-react-app my-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vercel/next.js/tree/canary/packages/create-next-app&quot;&gt;Next.js&lt;/a&gt; (&lt;code&gt;npx create-next-app@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/nuxt/create-nuxt-app&quot;&gt;Nuxt.js&lt;/a&gt; (&lt;code&gt;npx create-nuxt-app my-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sveltejs/kit/tree/master/packages/create-svelte&quot;&gt;SvelteKit&lt;/a&gt; (&lt;code&gt;npm create svelte@latest my-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remix-run/remix/tree/main/packages/create-remix&quot;&gt;Remix&lt;/a&gt; (&lt;code&gt;npx create-remix@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vercel/turborepo/tree/main/packages/create-turbo&quot;&gt;Turborepo&lt;/a&gt; (&lt;code&gt;npx create-turbo@latest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vuejs/create-vue#create-vue&quot;&gt;Vue&lt;/a&gt; (&lt;code&gt;npm init vue@3&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How to get started with improving site performance</title><link>https://petermekhaeil.com/how-to-get-started-performance/</link><guid isPermaLink="true">https://petermekhaeil.com/how-to-get-started-performance/</guid><description>With so many techniques on improving site performance - how does one start learning about site performance and what are the first steps? Learn about the tools like Google Lighthouse, PageSpeed Insights and WebPageTest.</description><pubDate>Fri, 18 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With so many techniques on improving site performance - how does one start learning about site performance and what are the first steps?&lt;/p&gt;
&lt;p&gt;Going to break it into 3 sections that should be explored:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Know your application&lt;/li&gt;
&lt;li&gt;Know your HTML&lt;/li&gt;
&lt;li&gt;Know your tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Know your application&lt;/h3&gt;
&lt;p&gt;Your application is like a high-performance car owned by a motor sport team. How does the team improve their lap times? By improving the cars performance. How does the team improve the cars performance? By first knowing what is in your car.&lt;/p&gt;
&lt;p&gt;Get to know what is in your application, get to know what is being bundled inside because the size of your script files will have an impact on performance and reducing bundle size is one of the first techniques I recommend to getting started on improving site performance. Most modern web applications are bundled with many dependencies and the first step in knowing what to improve is by first knowing what libraries are being imported into your application. Get curious about the size of your application.&lt;/p&gt;
&lt;p&gt;One of the first tools that I started using is &lt;a href=&quot;https://github.com/webpack-contrib/webpack-bundle-analyzer&quot;&gt;webpack-bundle-analyzer&lt;/a&gt;. It generates a report of the output of your webpack build which visualizes the file size of your JavaScript files and what libraries are included in each file. Here is an example screenshot:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;So we know what is in our application. What can we do with this information?&lt;/p&gt;
&lt;p&gt;We can see &lt;code&gt;lodash&lt;/code&gt; takes a large portion of the file. Lodash was one of the first libraries that got me interested in reducing bundle size because it was always included in projects when I first started in my software development career. This is where my curiosity kicked in - why was it so large every time, regardless of the functions that I was using?&lt;/p&gt;
&lt;p&gt;After some researching, I came to learn that how you import your dependencies, and how your dependencies were build makes a difference. Using &lt;code&gt;lodash&lt;/code&gt; as an example - the package is not &lt;a href=&quot;https://en.wikipedia.org/wiki/Tree_shaking&quot;&gt;tree-shakable&lt;/a&gt;. So instead of doing this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { difference, isEqual } from &apos;lodash&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Importing the features directly would avoid the complete package from being imported into the bundle:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import difference from &apos;lodash/difference&apos;;
import isEqual from &apos;lodash/isEqual&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can see that instead of the whole package being imported, only the modules that were required are being imported.&lt;/p&gt;
&lt;p&gt;This technique doesn&apos;t work for every library because it depends on how it is built. The idea here is - get curious about the size of your application. Understand the dependencies being imported and why are they are being imported. There could be better ways to import only the code you need for your application.&lt;/p&gt;
&lt;p&gt;A great tool to help you know the size of a packages is &lt;a href=&quot;https://bundlephobia.com/&quot;&gt;Bundlephobia&lt;/a&gt;. It&apos;s great for times when you want to know the file size cost of a package before importing it into your application. It also suggests similar libraries and the file size comparison.&lt;/p&gt;
&lt;p&gt;For example - if you look at the bundle report above, you will see &lt;code&gt;moment&lt;/code&gt; being imported and it looks large enough for us to investigate if it can be reduced. Bundlephobia will suggest smaller packages:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Replacing &lt;code&gt;moment&lt;/code&gt; with a &lt;code&gt;dayjs&lt;/code&gt;, a much smaller alternative with very similar API:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;By looking at the bundle size, I was able to drop the JavaScript file size from 868.16 KB to 151.43 KB (83% smaller bundle!).&lt;/p&gt;
&lt;p&gt;Small note: &lt;code&gt;moment&lt;/code&gt; was a popular library before it went into &lt;a href=&quot;https://momentjs.com/docs/#/-project-status/&quot;&gt;maintenance mode&lt;/a&gt; - highly recommend you to start using the other alternative libraries instead.&lt;/p&gt;
&lt;h3&gt;Know your HTML&lt;/h3&gt;
&lt;p&gt;Let&apos;s go back to the basics. A HTML page has a &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt;. The &lt;code&gt;head&lt;/code&gt; is used for the &quot;metadata&quot; of the page, information that is not rendered. &lt;code&gt;body&lt;/code&gt; is the document that is being rendered to the viewer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;My Website&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;./fancy-script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./really-large-stylesheet.css&quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;Welcome to my little place in the web.&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where you place your JavaScript and stylesheets has an impact on how your page renders. The &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot;&amp;gt;&lt;/code&gt; in the example above is considered render-blocking. Which means they must be loaded and processed before the browser renders the page.&lt;/p&gt;
&lt;p&gt;Why is that? Because browsers follow rules to determine if a resource is render-blocking.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/render-blocking-resources/#which-urls-get-flagged-as-render-blocking-resources&quot;&gt;A web.dev article on Eliminate render-blocking resources&lt;/a&gt; explains perfectly the rules:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document.&lt;/li&gt;
&lt;li&gt;Does not have a &lt;code&gt;defer&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;Does not have an &lt;code&gt;async&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot;&amp;gt;&lt;/code&gt; tag that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does not have a &lt;code&gt;disabled&lt;/code&gt; attribute. When this attribute is present, the browser does not download the stylesheet.&lt;/li&gt;
&lt;li&gt;Does not have a &lt;code&gt;media&lt;/code&gt; attribute that matches the user&apos;s device.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this knowledge and the techniques explains in the article - The HTML sample from above can be improved:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;My Website&amp;lt;/title&amp;gt;
    &amp;lt;!-- Defer non critical scripts not required for first render --&amp;gt;
    &amp;lt;script src=&quot;./fancy-script.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;!-- Inline critical styling required for first render --&amp;gt;
    &amp;lt;style type=&quot;text/css&quot;&amp;gt;
      h1 {
        color: blue;
        font-weight: bold;
        text-align: center;
      }
    &amp;lt;/style&amp;gt;
    &amp;lt;!-- Preload stylesheets not required for first render --&amp;gt;
    &amp;lt;link
      rel=&quot;preload&quot;
      href=&quot;./non-critical-stylesheet.css&quot;
      as=&quot;style&quot;
      onload=&quot;this.onload=null;this.rel=&apos;stylesheet&apos;&quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Hello world!&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;Welcome to my little place in the web.&amp;lt;/p&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take a look at the HTML source code of projects or your favourite websites and see if there are ways that the scripts and stylesheets can be re-arranged to improve the site performance. This can be a great way to get started and get familiar with how browsers handle resource requests.&lt;/p&gt;
&lt;h3&gt;Know your tools&lt;/h3&gt;
&lt;p&gt;Tools that can help you get started on improving site performance:&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://developers.google.com/web/tools/lighthouse&quot;&gt;Google Lighthouse&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Provides insights on best practices for your websites. It can run from Chrome DevTools or from a CLI. It scores your website on the Core Web Vitals and also provides suggestions on how to improve page load speed.&lt;/p&gt;
&lt;p&gt;This is a great place to start. Run it on your own websites to get familiar with some of the suggestions reported and use them to explore many other techniques in improving performance. The suggestions also link to &lt;a href=&quot;https://web.dev/&quot;&gt;web.dev&lt;/a&gt; articles that go in details on why these suggestions are listed.&lt;/p&gt;
&lt;p&gt;Lighthouse reports on &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#lab_data&quot;&gt;Lab Data&lt;/a&gt; which means the scores may change each time depending on &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/blob/master/docs/variability.md&quot;&gt;network conditions&lt;/a&gt;, so do remember to run it a few times before finalizing any solutions.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Analyses the performance of a website and generates a report that provides suggestions on what can be improved. It provides Lab Data powered by Google Lighthouse and is always updated when a new version of Lighthouse is released.&lt;/p&gt;
&lt;p&gt;It also reports on &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#field_data&quot;&gt;Field Data&lt;/a&gt; from real users collected from &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report/&quot;&gt;Chrome UX Report&lt;/a&gt;, which is a great way to report on how your users are experiencing your website.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Measures your website performance using different locations, different browsers and different devices. It runs three tests and provides a summary which includes a waterfall view that chronological lists all the resources that were downloaded.&lt;/p&gt;
&lt;p&gt;The other features that will come useful to you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can generate &lt;a href=&quot;https://www.webpagetest.org/video/&quot;&gt;videos comparison&lt;/a&gt; of how your website loads against other sites.&lt;/li&gt;
&lt;li&gt;Highlights which element on the page was the &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;largest contentful paint&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Generates videos of any &lt;a href=&quot;https://web.dev/cls/&quot;&gt;layout shifts&lt;/a&gt; that occurs on your website.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The waterfall view is very useful in understanding the load times of each resources being downloaded and which were potentially blocking you from having a faster website. For example - when running a test on my site, we can see 2 resources were downloaded before first render:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I can improve the load times even further by inlining the critical styling and lazy loading the image because it is found at the bottom of the page.&lt;/p&gt;
&lt;p&gt;I go a little further with WebPageTest in &lt;a href=&quot;https://petermekhaeil.com/page-speed-comparison-of-singapore-commerce/&quot;&gt;Page Speed Comparison of Singapore Commerce&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Where to go from here?&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev&quot;&gt;web.dev&lt;/a&gt; is a great place to learn everything you need on making your websites faster. Start with the &lt;a href=&quot;https://web.dev/fast/&quot;&gt;Fast load times&lt;/a&gt; to learn techniques on improving site performance.&lt;/p&gt;
&lt;p&gt;Once you got a hang over some of those techniques, take a look at the &lt;a href=&quot;https://web.dev/metrics/&quot;&gt;Metrics&lt;/a&gt; course to learn the metrics used to report on site performance.&lt;/p&gt;
&lt;p&gt;When you are ready, head over to the &lt;a href=&quot;https://web.dev/learn-web-vitals/&quot;&gt;Web Vitals&lt;/a&gt; course to learn all about the Core Web Vitals and why these metrics are important for a great user experience.&lt;/p&gt;
</content:encoded></item><item><title>Jamstack vs Traditional Web</title><link>https://petermekhaeil.com/jamstack-vs-traditional-web/</link><guid isPermaLink="true">https://petermekhaeil.com/jamstack-vs-traditional-web/</guid><description>Learn about what is Jamstack and how it compares to a traditional website. We go through the steps involved in building one, the components that make up a Jamstack site and its advantages and limitations.</description><pubDate>Sat, 11 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Traditional Web&lt;/h2&gt;
&lt;p&gt;Here is a story about a popular online store - Uncle Bobba’s Bubble Tea. Uncle Bobba had a traditional web application that looked like this:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is a simplified diagram and the idea behind this is to show you a rough example of how a typical e-commerce website looks like. It’s not limited to auth and payment services and can also have other moving systems. When the user requests a page, the server is generating the content on the server side before returning the content back to the user.&lt;/p&gt;
&lt;p&gt;It works. It does it’s job. Uncle Bubba had happy customers.&lt;/p&gt;
&lt;p&gt;Then came a time when Uncle Bubba decided to release a new product. The news spread fast across the land and Uncle Bubba found a lot of customers coming to his store:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Suddenly, things did not go to plan. The team soon discovered the pages were not responding. There was lag in server response and API calls between services were timing out. The systems were so heavily coupled together and it was too difficult to pin-point where it went wrong. Not long after, Uncle Bubba found himself with some angry customers who didn’t get to enjoy his new bubble tea flavours.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This could be caused by many reasons in Uncle Bubba&apos;s architecture and there is a high chance of happening in systems that are highly coupled with so many moving parts.&lt;/p&gt;
&lt;p&gt;Surely there is a way to solve this. After all, we are only serving a product catalog of some text and images. What if we can remove this black box (the server in the diagram) and allow the users to interact directly with the HTML without needing a server to generate the content on the fly every time the user requests the page?&lt;/p&gt;
&lt;p&gt;This is where Jamstack comes to play.&lt;/p&gt;
&lt;h2&gt;What is Jamstack?&lt;/h2&gt;
&lt;p&gt;Statically generating your UI application and decoupling it from your other systems.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Javascript&lt;/strong&gt; Client-side interactions such as navigation and talking to API services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;API&lt;/strong&gt; Interact with third-party services that are decoupled from each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Markdown&lt;/strong&gt; Prebuilt HTML pages generated using a static site generator.&lt;/p&gt;
&lt;p&gt;Using your favourite front-end framework (eg React, Vue, Svelte) to build a pre-rendered copy of your UI application and deploying it on a CDN. Think of it like taking a snapshot of your UI and storing it on the cloud. Your users do not need to interact with your server.&lt;/p&gt;
&lt;h2&gt;How is a Jamstack site generated?&lt;/h2&gt;
&lt;p&gt;This happens in the continuous-integration (CI) build pipeline:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source code&lt;/strong&gt; It all starts in the git repository. Jamstack requires the application stored in a version control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static site generator&lt;/strong&gt; On a new commit, a build step is triggered and the static site generator will build the application. During the build, if the application requires any content from an external data source, the static site generator will fetch that data. This can be from any external service that provides content in a format that the static site generator supports. Example of data sources: Headless CMS, databases, Markdown.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static content&lt;/strong&gt; Once the package is bundled and optimized for the web, it is deployed to a CDN. Which is an important element of Jamstack and it one of many reasons why Jamstack is so fast - the pre-rendered static HTML content is served around the world.&lt;/p&gt;
&lt;p&gt;This process of building a Jamstack application moves the server generation of your page to the build pipeline.&lt;/p&gt;
&lt;h2&gt;How does it look like now that there is no server?&lt;/h2&gt;
&lt;p&gt;There is no connection with the users and the build pipeline. The users are only requesting the prebuilt HTML markdown that has been served from the CDN and the user is only interacting with the client-side APIs.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Why Jamstack?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lower costs:&lt;/strong&gt; No server costs (only paying for storage).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; No user-facing systems required to serve the content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO:&lt;/strong&gt; Crawlers work better with pre-rendered content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Static pages served from CDN.&lt;/li&gt;
&lt;li&gt;No flash of unstyled content.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web-optimized image rendering:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Fetch above-the-fold images immediately.&lt;/li&gt;
&lt;li&gt;Delay rendering of off-screen images.&lt;/li&gt;
&lt;li&gt;Image placeholders to avoid layout shifts.&lt;/li&gt;
&lt;li&gt;Automatically compressed and optimized for web.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Any limitations?&lt;/h2&gt;
&lt;p&gt;Some limitations that the community and static-site-generators are working on solving:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Previewing content before publishing can be challenging.&lt;/li&gt;
&lt;li&gt;Long builds times if there are a large number of pages to generate.&lt;/li&gt;
&lt;li&gt;Not suitable for dynamic content.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By moving away from a traditional architecture, an online e-commerce store can now handle a large number of users by serving statically generated pages from a CDN - fast rendering pages decoupled from the underlying systems that were used to generate the content.&lt;/p&gt;
&lt;p&gt;Jamstack decouples your UI from your other systems, making it a powerful approach in building high performing applications that can scale and is cost-efficient.&lt;/p&gt;
</content:encoded></item><item><title>Measuring the performance of the McLaren Racing website</title><link>https://petermekhaeil.com/measuring-the-performance-of-the-mclaren-racing-websitemd/</link><guid isPermaLink="true">https://petermekhaeil.com/measuring-the-performance-of-the-mclaren-racing-websitemd/</guid><description>We look at the McLaren Racing website and highlight improvements that will speed up their web performance. We will use tools like PageSpeed Insights and WebPageTest to share techniques on improving page speed.</description><pubDate>Sat, 19 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With &lt;a href=&quot;https://www.mclaren.com/racing/&quot;&gt;McLaren Racing&lt;/a&gt; recently &lt;a href=&quot;https://twitter.com/McLarenF1/status/1504133329855164420&quot;&gt;announced a partnership&lt;/a&gt; with Google Chrome, this is a great opportunity for McLaren to measure the performance of their website and look into how it can be improved. Today, we will look at their performance scores and talk about some possible improvements that McLaren can do to their website performance.&lt;/p&gt;
&lt;h2&gt;How the McLaren Racing website will be measured&lt;/h2&gt;
&lt;p&gt;We will use a couple tools to measure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt; - an online tool that aggregate the scores and compares the websites of the teams involved in Formula 1.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/speed/docs/insights/v5/about&quot;&gt;PageSpeed Insights&lt;/a&gt; is an online tool that reports on site performance and it includes both &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#lab_data&quot;&gt;lab data&lt;/a&gt; and &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/speed-tools/#field_data&quot;&gt;real-world field data&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; can test websites from different locations around the world using different browsers and can provide in-depth analysis in a site’s performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An earlier post was written on the page speed performance of Formula 1 websites which explains in details how web performance scores are measured. Read “&lt;a href=&quot;https://petermekhaeil.com/page-speed-performance-of-formula-1-websites/&quot;&gt;Page Speed Performance of Formula 1 Websites&lt;/a&gt;”.&lt;/p&gt;
&lt;h2&gt;The performance score of the McLaren Racing website&lt;/h2&gt;
&lt;p&gt;Using &lt;a href=&quot;https://f1-page-speed-insights.netlify.app/&quot;&gt;F1 Page Speed Insights&lt;/a&gt; we can see how McLaren Racing is doing against the other teams. Lots of room for improvement if McLaren wants podium wins this year.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The detailed breakdown on McLaren Racing’s website shows that it did not pass the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt; assessment. We will look at some possible improvements that can bump the score up.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;The current state of the McLaren Racing website&lt;/h2&gt;
&lt;p&gt;The filmstrip generated by &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; is a good tool to see the rough timing of elements as they start to appear on the page. Here we can see the main background starts to load around the 6 second mark.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;WebPageTest also provides a waterfall of the network requests made by the page. We can use this to understand the sequence of the requests and their timings:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Possible Improvements to the McLaren Racing website&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;Avoid blocking resources on other domains&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Row 5 of the waterfall &lt;a href=&quot;https://web.dev/render-blocking-resources/&quot;&gt;shows a render-blocking CSS&lt;/a&gt; that is loaded in the head of the page. The thin line at the start of the request is the connection setup because it is located on a different domain. We can prepare this connection set up earlier by using &lt;code&gt;&amp;lt;link rel=&quot;preload&quot;/&amp;gt;&lt;/code&gt; to initiate the download sooner.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Learn more about how to &lt;a href=&quot;https://web.dev/preload-critical-assets/&quot;&gt;preload critical assets to improve loading speed&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Avoid loading unused CSS&lt;/h3&gt;
&lt;p&gt;A deeper look into the CSS from above and we can see that 94.7% of it is unused on the landing page - it is render-blocking and mostly unused on this page. We can cut a lot of the 500ms if we only load the CSS required on this page (we can also just remove this CSS file entirely and inline the small amount of CSS that was used).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Learn more about &lt;a href=&quot;https://web.dev/unused-css-rules&quot;&gt;removing unused CSS&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Preconnect for early connection setup&lt;/h3&gt;
&lt;p&gt;The McLaren Racing website loads the assets from different domains which has a connection cost (indicated by the thin line that appears before the requests in the waterfall chart). We can &lt;code&gt;preconnect&lt;/code&gt; to those domains to let the browser know we intend to make a request to those domains and so the browser will prepare the connection early and avoid having to do it later when we make the requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://static-cdn.mclaren.com&quot; /&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://media-cdn.mclaren.com&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Learn more about &lt;a href=&quot;https://web.dev/preconnect-and-dns-prefetch/&quot;&gt;establishing network connections early to improve perceived page speed&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Avoid blocking parsing with JavaScript&lt;/h3&gt;
&lt;p&gt;The JavaScript file on Row 6 is parser-blocking. It is located in the body in the document and the browser has decided to parse and execute this JavaScript file before it attempts to download the other assets on the page. This can be seen on this waterfall. Row 35 is the main hero background image which has a “wait” time because the browser was waiting for Row 6 to finish executing. This was also seen in the filmstrip video above. By using the &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; attribute on the JavaScript file on Row 6, 7 and 8, we can remove the render-blocking and the rest of the images on the page can be parsed earlier. And in return, the main background image may possibly load earlier which will bring up the Core Web Vital score. A good illustration on how these attributes work can be found on &lt;a href=&quot;https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html&quot;&gt;async vs defer attributes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Learn more about &lt;a href=&quot;https://web.dev/render-blocking-resources/&quot;&gt;eliminating render-blocking resources&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Optimise images for the web&lt;/h3&gt;
&lt;p&gt;The biggest opportunity to improve the McLaren Racing website could be in optimising the large images that they are serving. Here is a screenshot from Google Lighthouse pointing out the possible savings if we are to optimise the images:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The images on the McLaren Racing website aren’t optimised for the web and there is over 13MB worth of these large high-quality images. We can use &lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh&lt;/a&gt; to reduce the file size of these images while maintaining quality.&lt;/p&gt;
&lt;p&gt;Doing a quick Squoosh run on the top 3 sized images, there is a cost savings of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Samsung-Galaxy-S9-v3.png: 805.4 KB -&amp;gt; 88.4 KB &lt;strong&gt;(-89.0%)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;mclarenplus.jpg: 240.1 KB -&amp;gt; 36.3 KB &lt;strong&gt;(-84.9%)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;2022-drive-to-survive-4.jpg: 161.1 KB -&amp;gt; 104.5 KB &lt;strong&gt;(-35.1%)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using AVIF format we can even reduce the size even further. AVIF is not supported on all browsers but we can progressively support those that do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source type=&quot;image/avif&quot; srcset=&quot;img.avif&quot; /&amp;gt;
  &amp;lt;img src=&quot;img.jpg&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Learn more about &lt;a href=&quot;https://web.dev/uses-webp-images/&quot;&gt;serving images in modern formats&lt;/a&gt; and &lt;a href=&quot;https://web.dev/uses-optimized-images/&quot;&gt;efficiently encoding images&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Lazy load images that are below-the-fold&lt;/h3&gt;
&lt;p&gt;Below-the-fold is a term that describes the area of the website that the user cannot see unless they scroll down. Most of the images on the McLaren Racing website are below-the-fold and they are being downloaded even though they are not in view. We can avoid the requests being made by lazy loading those images using &lt;code&gt;&amp;lt;img loading=&quot;lazy&quot; /&amp;gt;&lt;/code&gt; so they are only requested as the user scrolls closer to those images. This will improve performance and save bandwidth.&lt;/p&gt;
&lt;p&gt;Learn more about &lt;a href=&quot;https://web.dev/lazy-loading-images/&quot;&gt;lazy-loading images&lt;/a&gt; and &lt;a href=&quot;https://web.dev/browser-level-image-lazy-loading/&quot;&gt;browser-level image lazy-loading for the web&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Summary of opportunities&lt;/h2&gt;
&lt;p&gt;Recapping the key focus areas that McLaren Racing can take advantage of to improve their web performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avoid blocking resources on other domains.&lt;/li&gt;
&lt;li&gt;Avoid loading unused CSS.&lt;/li&gt;
&lt;li&gt;Preconnect for early connection setup.&lt;/li&gt;
&lt;li&gt;Avoid blocking parsing with JavaScript.&lt;/li&gt;
&lt;li&gt;Optimise images for the web.&lt;/li&gt;
&lt;li&gt;Lazy load images that are below-the-fold.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>How to keep undefined values in JSON.stringify</title><link>https://petermekhaeil.com/json-stringify-replacer-parameter/</link><guid isPermaLink="true">https://petermekhaeil.com/json-stringify-replacer-parameter/</guid><description>Learn how to use the replacer parameter of JSON.stringify to replace undefined values.</description><pubDate>Mon, 22 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://www.json.org/json-en.html&quot;&gt;JSON spec&lt;/a&gt; does not allow &lt;code&gt;undefined&lt;/code&gt; values, so when you try to stringify an object that contains an undefined value, the key will get removed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const person = { name: &apos;Peter&apos;, age: undefined };

JSON.stringify(person);
// &apos;{&quot;name&quot;:&quot;Peter&quot;}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are workarounds to keep &lt;code&gt;undefined&lt;/code&gt; in the return value, but because it is not part of spec, you will not be able to parse the string back to a JSON. Instead, we can replace it with &lt;code&gt;null&lt;/code&gt; which is valid.&lt;/p&gt;
&lt;p&gt;JSON.stringify has an optional second parameter &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter&quot;&gt;replacer&lt;/a&gt; that can recursively transform properties during the stringify process.&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;replacer&lt;/code&gt; parameter of JSON.stringify, we can replace &lt;code&gt;undefined&lt;/code&gt; values with a value that is accepted - such as &lt;code&gt;null&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const person = { name: &apos;Peter&apos;, age: undefined };

function replacer(key, value) {
  return value === undefined ? null : value;
}

JSON.stringify(person, replacer);
// &apos;{&quot;name&quot;:&quot;Peter&quot;,&quot;age&quot;:null}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example use case where this can come useful is network request payloads in the case we need to let the server know which values are not defined. POST requests have a string body, if we need to send over an object, we require to &lt;code&gt;JSON.stringify&lt;/code&gt; the request. The above approach will help us send over values that are not defined (provided that the server understands &lt;code&gt;null&lt;/code&gt; as an acceptable value).&lt;/p&gt;
</content:encoded></item><item><title>How to build an app with Remix and Netlify Graph</title><link>https://petermekhaeil.com/netlify-graph-with-remix/</link><guid isPermaLink="true">https://petermekhaeil.com/netlify-graph-with-remix/</guid><description>Learn how to build an app using Remix that connects with third-party APIs using Netlify Graph. We will fetch data from GitHub and use React JSX to build the UI.</description><pubDate>Thu, 03 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial, you will learn how to build an app using Remix and use Netlify Graph to talk to third-party APIs. We will use Netlify Graph to fetch data from GitHub and show the user a collection of repository issues. Along the way we will learn about the tools chosen and why they are a great fit for our use case.&lt;/p&gt;
&lt;h3&gt;Source code and demo&lt;/h3&gt;
&lt;p&gt;The source code for this app can be found &lt;a href=&quot;https://github.com/petermekhaeil/netlify-graph-remix&quot;&gt;in this repository&lt;/a&gt;. Add an issue to the repository (or add a comment to an existing issue) to see the changes get reflected instantly on the &lt;a href=&quot;https://netlify-graph-remix.netlify.app/&quot;&gt;demo app&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;What is Remix&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://remix.run/&quot;&gt;Remix&lt;/a&gt; is a full stack web framework that let&apos;s you focus on the user interface and work back through web fundamentals to deliver a fast, slick, and resilient user experience. Remix is great for our use case because it generates fast responding pages on the server so we can get the latest data from GitHub each time the user hits our pages.&lt;/p&gt;
&lt;h3&gt;What is Netlify Graph&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/netlify/labs/tree/main/features/graph/documentation&quot;&gt;Netlify Graph&lt;/a&gt; provides developers with a GraphQL-based approach to integrating with third-party APIs without needing to operate a separate GraphQL server. Developers do not need to spend time learning the API structure of each provider and writing code to piece it together. This is great for our use case because we do not want to worry about how to authenticate with GitHub as Netlify will handle this for us.&lt;/p&gt;
&lt;h3&gt;Getting Started with Remix&lt;/h3&gt;
&lt;p&gt;Create a new Remix site. Choose &quot;Netlify&quot; when prompted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npx create-remix@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install dependencies&lt;/h3&gt;
&lt;p&gt;We will need to install some dependencies for our app to work. Change directory to your new project and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install slugify marked
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;slugify&lt;/strong&gt;: Generates URL-friendly paths based on a text string. We will use to generate URLs based on the title of our GitHub issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;marked&lt;/strong&gt;: Markdown parser to convert Markdown to HTML. We will use it to render the GitHub issue body onto our HTML page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a GitHub Repository&lt;/h3&gt;
&lt;p&gt;You will want to &lt;a href=&quot;https://github.com/new&quot;&gt;create a new repository in GitHub&lt;/a&gt; and commit the code that was generated in the earlier step. This will be the repository that we will be fetching data from using Netlify Graph.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd [name of project]
$ git init
$ git add .
$ git commit -m &quot;first commit&quot;
$ git remote add origin https://github.com/your-username/name-of-project.git
$ git add push -u origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Install the Netlify CLI&lt;/h3&gt;
&lt;p&gt;Install the latest version of Netlify CLI and log into your Netlify account:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm i -g netlify-cli@latest
$ ntl login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change directory to your new project (if you have not done so already) and create a new Netlify site:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd [name of project]
$ ntl init
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Start Development Server&lt;/h3&gt;
&lt;p&gt;Start the Netlify Dev Server with the &lt;code&gt;--graph&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ntl dev --graph
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start up a local development server that we will use to build our app. It will also start up a Netlify Graph session that we will use in the next section.&lt;/p&gt;
&lt;h3&gt;Create a GraphQL query with the Graph Explorer&lt;/h3&gt;
&lt;p&gt;Select your site from your &lt;a href=&quot;https://app.netlify.com/&quot;&gt;Netlify team dashboard&lt;/a&gt; and then select “Graph” from your Netlify site dashboard menu. Click on &quot;Connect API or service&quot;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Netlify Graph supports a handful of API providers that we can connect with:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We are going to connect to GitHub. Select &quot;GitHub&quot; from the list of providers and you will need to enable both &quot;Authentication&quot; and &quot;Graph Explorer&quot;. Then continue with &quot;Start querying GitHub&quot;:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once you have selected your open session (that was created when you started the dev server), you will be presented with a Graph Explorer. This will allow you to query the GitHub API for the data you are interested in fetching. For our use case, we will want to fetch the GitHub repository issues and their comments.&lt;/p&gt;
&lt;p&gt;Below is a GraphQL query with &lt;code&gt;Issues&lt;/code&gt; as its operation name. Copy the below snippet and paste it in the explorer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;query Issues(
  $owner: String = &quot;petermekhaeil&quot;
  $name: String = &quot;netlify-graph-remix&quot;
) {
  gitHub {
    repository(name: $name, owner: $owner) {
      issues(first: 100, states: OPEN) {
        edges {
          node {
            id
            body
            title
            createdAt
            url
            comments(first: 100) {
              nodes {
                body
                createdAt
                id
                author {
                  avatarUrl
                  login
                }
                url
              }
            }
          }
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After clicking &quot;Save Changes&quot;, you can head back to your IDE and you will notice some changes that have been made to your workspace. A new &lt;code&gt;netlifyGraph&lt;/code&gt; folder has appeared:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netlify/functions/netlifyGraph/index.d.ts
netlify/functions/netlifyGraph/index.js
netlify/functions/netlifyGraph/netlifyGraphOperationsLibrary.graphql
netlify/functions/netlifyGraph/netlifyGraphSchema.graphql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Netlify Graph has generated code for us to use to fetch the data from our app.&lt;/p&gt;
&lt;h3&gt;Fetching Data from Remix&lt;/h3&gt;
&lt;p&gt;The Remix app we created earlier came with a demo index page. We can remove it and replace the content of &lt;code&gt;./app/routes/index.jsx&lt;/code&gt; with the code below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Link, useLoaderData } from &apos;remix&apos;;
import slugify from &apos;slugify&apos;;
import NetlifyGraph from &apos;../../netlify/functions/netlifyGraph&apos;;

export const loader = async () =&amp;gt; {
  const { data } = await NetlifyGraph.fetchIssues(
    { owner: &apos;petermekhaeil&apos;, name: &apos;netlify-graph-remix&apos; },
    { accessToken: process.env.ONEGRAPH_AUTHLIFY_TOKEN }
  );

  return data.gitHub.repository.issues.edges.map(({ node }) =&amp;gt; {
    return {
      ...node,
      slug: slugify(node.title).toLowerCase()
    };
  });
};

export default function Index() {
  let data = useLoaderData();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Issues&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.map((item) =&amp;gt; (
          &amp;lt;li key={item.id}&amp;gt;
            &amp;lt;Link to={item.slug}&amp;gt;{item.title}&amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walking through the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;loader&lt;/code&gt; function is a built-in data loading Remix API. Each Remix route is served from the server and each route can fetch external data before generating the HTML and returning it back to the user. It is in this function that we will fetch data from Netlify Graph and use it in the React template. Learn more about the &lt;code&gt;loader&lt;/code&gt; function on the &lt;a href=&quot;https://remix.run/docs/en/v1/api/conventions#loader&quot;&gt;Remix Docs&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;NetlifyGraph.fetchIssues&lt;/code&gt; is the generated function from Netlify Graph that we will use to fetch the data from GitHub. This was generated when you clicked &quot;Save Changes&quot; in the Netlify Graph Explorer from the earlier step. You can replace the value of &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; to your own repository, or use the default value if you wish.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;useLoaderData&lt;/code&gt; hook from Remix will return the data from the &lt;code&gt;loader&lt;/code&gt; function so we can use it in the React template.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refresh your browser and you will see Remix rendering the list of issues from the GitHub repository.&lt;/p&gt;
&lt;p&gt;There are two things we should note from the above code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Authentication will be taken care by Netlify regardless of which API provider you decide to work with. This is an awesome feature of Netlify Graph because it allows us not to worry about the structures of the third-party APIs. Netlify takes care of this by handling the value of the &lt;code&gt;ONEGRAPH_AUTHLIFY_TOKEN&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netlify Graph generated the JS function with the name &lt;code&gt;fetchIssues&lt;/code&gt; because the GraphQL query that we wrote had the operation name as &lt;code&gt;Issues&lt;/code&gt;. We can have many operations in the GraphQL Explorer and Netlify Graph will generate a JS function for each one with unique names based on the operation name.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Dynamic Routes in Remix&lt;/h3&gt;
&lt;p&gt;Instead of creating each page manually by hand, Remix can create a route for each of the GitHub issue dynamically.&lt;/p&gt;
&lt;p&gt;We can create a dynamic route by creating &lt;code&gt;./app/routes/$slug.jsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useLoaderData } from &apos;remix&apos;;
import NetlifyGraph from &apos;../../netlify/functions/netlifyGraph&apos;;
import slugify from &apos;slugify&apos;;
import { marked } from &apos;marked&apos;;

async function getLoaderData(title) {
  const { data } = await NetlifyGraph.fetchIssues(
    { owner: &apos;petermekhaeil&apos;, name: &apos;netlify-graph-remix&apos; },
    { accessToken: process.env.ONEGRAPH_AUTHLIFY_TOKEN }
  );

  const post = data.gitHub.repository.issues.edges.find(({ node }) =&amp;gt; {
    return slugify(node.title).toLowerCase() === title;
  });

  return post?.node;
}

export const loader = async ({ params }) =&amp;gt; {
  const post = await getLoaderData(params.slug);

  const html = marked(post.body);

  return {
    ...post,
    slug: params.slug,
    html
  };
};

export default function PostSlug() {
  const post = useLoaderData();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;div dangerouslySetInnerHTML={{ &quot;{{ __html: post.html }}&quot; }} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walking through the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;getLoaderData&lt;/code&gt; will fetch the GitHub data via Netlify Graph and filter for the GitHub issue we want by the value of &lt;code&gt;title&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We have been introduced to &lt;code&gt;params&lt;/code&gt; in the &lt;code&gt;loader&lt;/code&gt; function. &lt;code&gt;params&lt;/code&gt; contain the dynamic key that is attached after the &lt;code&gt;$&lt;/code&gt; in the filename. In our example, the filename &lt;code&gt;$slug.jsx&lt;/code&gt; will return &lt;code&gt;params.slug&lt;/code&gt; with the value from the URL. We will use this value as the title of the GitHub issue we want to display on this page.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now if you refresh the page and click on any of the GitHub issues, you&apos;ll be redirected to a page with the full issue content. An example of a dynamic route from the demo app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://netlify-graph-remix.netlify.app/getting-started-with-remix-and-netlify-graph&quot;&gt;/getting-started-with-remix-and-netlify-graph&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What we learnt today&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Each Remix route supports a &lt;a href=&quot;https://remix.run/docs/en/v1/api/conventions#loader&quot;&gt;loader&lt;/a&gt; function that is called on the server before rendering. It provides data to the route and can be used in the React template.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remix supports &lt;a href=&quot;https://remix.run/docs/en/v1/api/conventions#dynamic-route-parameters&quot;&gt;dynamic routes&lt;/a&gt; by using the &lt;code&gt;$&lt;/code&gt; filename convention.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://remix.run/docs/en/v1/tutorials/jokes#expected-errors&quot;&gt;Expected errors&lt;/a&gt; can be caught in Remix and we can render user-friendly messages using a &lt;a href=&quot;https://remix.run/docs/en/v1/api/conventions#catchboundary&quot;&gt;CatchBoundary&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netlify Graph uses a Graph Explorer to query the data we need and is synced with the session opened by the CLI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netlify Graph generates code that we can use to fetch data from a third-party API without needing to worry about authentication or understanding the API structure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Notes - The Coding Career Handbook</title><link>https://petermekhaeil.com/notes-the-coding-career-handbook/</link><guid isPermaLink="true">https://petermekhaeil.com/notes-the-coding-career-handbook/</guid><description>Notes from The Coding Career Handbook - Learning in Public by Shawn Wang.</description><pubDate>Fri, 19 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;These are my learnings from reading &lt;a href=&quot;https://www.learninpublic.org/&quot;&gt;The Coding Career Handbook&lt;/a&gt; by &lt;a href=&quot;https://www.swyx.io/&quot;&gt;Shawn Wang&lt;/a&gt;. Notes for my future-self. I highly recommend this book for everyone of all levels in their career.&lt;/p&gt;
&lt;p&gt;Notes from Part I - Your Coding Career:&lt;/p&gt;
&lt;h3&gt;Chapter 4: Junior Developer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&quot;It is quite often that doing the things nobody else wants to do often ends up making you indispensable, because A) it has to be done and B) nobody else wants to do them. If you figure out a clever way of doing it, you could even make a career (or startup) out of it!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tests are the most common example of this. In fact, a really great way to start a new job is to volunteer to write tests.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“When one teaches, two learn.” - Robert Heinlein&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Chapter 5: From Junior to Senior&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Much of your learning from Junior to Senior involves gaining &lt;strong&gt;tacit knowledge&lt;/strong&gt;. You can read all the programming books in the world, but, by definition, you are still limited to things that people can write down. Tacit knowledge in engineering is a real thing. Keep a look out for all the lessons you don’t learn in classes or from books. To really make your career explode, make a habit of writing them down for everyone else (Chapter 18 - Write, A Lot!).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In particular, anything you do in public - blogging, speaking, podcasting, making video tutorials, open source work - can help you grow your knowledge and your network at the same time, opening up the possibility for inbound opportunities to come to you. Remember to fight Impostor Syndrome every step of the way!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Chapter 6: Senior Developer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&quot;The best way to be a 10x developer is to teach 10 people what you know.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The most valuable engineers are the ones who habitually view their work through the lens of what the business needs to succeed. Which is a skill you have to practice and foster and cultivate, just like any other.&quot; - Charity Majors&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Chapter 7: Beyond your Coding Career&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Becoming a manager is not a promotion - it’s a lateral move onto a parallel track. You’re back at junior level in many key skills.&quot; - Sarah Mei&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&quot;The best individual contributors are the ones who have done time in management. And the best technical leaders in the world are often the ones who do both. Back and forth. Like a pendulum.&quot;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Page Speed Comparison of Singapore Commerce</title><link>https://petermekhaeil.com/page-speed-comparison-singapore-commerce/</link><guid isPermaLink="true">https://petermekhaeil.com/page-speed-comparison-singapore-commerce/</guid><description>Performance comparison of Singapore E-commerce stores. We look at how the sites perform and the areas of improvement that can speed up their web performance. We will use Chrome UX Report to measure the Core Web Vitals of each store and how they compare against each other.</description><pubDate>Thu, 10 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An online commerce store that offers a great user experience will satisfy the needs of its users and page speed performance is a driving factor to this. This is where &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt; comes into play. The Core Web Vitals aim to measure user experience. It is a set of metrics that Google uses to measure user experience and it has become one of their key factors in search result rankings. It is also a great way to compare your commerce store against your competitors.&lt;/p&gt;
&lt;h2&gt;How this will work&lt;/h2&gt;
&lt;p&gt;10 popular online commerce stores were chosen from Singapore. We will score them based on a scoring system (defined below) and for a few of the sites, we will go into detail on how they performed and what improvements can be made to improve their score.&lt;/p&gt;
&lt;h2&gt;How Scoring Works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The scoring algorithm weighs values for the 3 metrics used in the Core Web Vitals: &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;, &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The metric with the lowest score is the final performance score.&lt;/li&gt;
&lt;li&gt;The data comes from the &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report/&quot;&gt;Chrome UX Report&lt;/a&gt; which is collected from real-world users over the last 28 days.&lt;/li&gt;
&lt;li&gt;It is an aggregated data over all form factors (desktop + mobile) and over all effective connection types.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Performance Score&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Live dashboard available at &lt;a href=&quot;https://sg-page-speed-insights.netlify.app/&quot;&gt;SG Page Speed Insights&lt;/a&gt;. You can check out the &lt;a href=&quot;https://github.com/petermekhaeil/page-speed-insights&quot;&gt;source code&lt;/a&gt; to see how the scores are calculated.&lt;/p&gt;
&lt;h2&gt;Analysing the stores in detail&lt;/h2&gt;
&lt;p&gt;We will use &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; and Chrome Dev Tools to analyse a few of these online stores and look at any areas of improvement that can help improve their scores. The test location of the WebPageTest test run will be Singapore and Chrome will be the browser.&lt;/p&gt;
&lt;p&gt;I&apos;ve chosen 3 sites from different markets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.fairprice.com.sg/&quot;&gt;FairPrice&lt;/a&gt; (Grocery Retailer)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.sephora.sg/&quot;&gt;Sephora&lt;/a&gt; (Beauty and Cosmetics)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.circles.life&quot;&gt;Circles.Life&lt;/a&gt; (Telecommunication)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;FairPrice&lt;/h3&gt;
&lt;p&gt;URL: &lt;a href=&quot;https://www.fairprice.com.sg/&quot;&gt;https://www.fairprice.com.sg/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We will start by looking at the request waterfall:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Remove unused JavaScript&lt;/h4&gt;
&lt;p&gt;The site is built using Next.js which is packed with features to help with performance. A noticeable feature is &lt;a href=&quot;https://nextjs.org/docs/advanced-features/dynamic-import&apos;&quot;&gt;Dynamic Importing&lt;/a&gt; which we can see from their chunk requests. By using Chrome Dev Tool&apos;s Coverage feature, let us see how much of these chunks are being used:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;1.5 MB (57%) of the chunks are not being used on page load. Reducing the amount of bytes the user needs to download will save the bandwidth and result in rendering the page faster.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Reduce HTML Size&lt;/h4&gt;
&lt;p&gt;Lets look at the very start request waterfall again:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;What we see here is that the page didn&apos;t render until around the 1.2s mark. The green vertical bar represents the start of first render. The browser is smart enough to render HTML while it is still downloading the rest of it. This tells us that there was no render-blocking resources - that is a good thing!&lt;/p&gt;
&lt;p&gt;There are two questions here though - why did it take 1.2 seconds to start rendering, and why did the HTML take 2.5 seconds to finish downloading? The answer could possibly be because of the size of the HTML - it is 269kb of compressed size. If we open up the HTML to see why its so large, we see the server side generated page content and if we look a little deeper down towards the end of the page, we can see a very large JSON script tag used in Next.js applications.&lt;/p&gt;
&lt;p&gt;The site is server side rendered using Next.js - once the page has been rendered, the browser needs to take over control of the page and this is called &lt;a href=&quot;https://reactjs.org/docs/react-dom.html#hydrate&quot;&gt;Hydration&lt;/a&gt;. The browser needs to understand the components being rendered and for that it requires the data that was used to generate this page. Next.js provides this through a JSON payload found in the HTML (the script has an ID &lt;code&gt;__NEXT_DATA__&lt;/code&gt;). If we grab this JSON from the website and run it through &lt;a href=&quot;https://www.debugbear.com/json-size-analyzer&quot;&gt;JSON Size Analyzer&lt;/a&gt; we can examine what sort of information is being send in the HTML.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s a pretty huge JSON embedded in the HTML. There is a big win for FairPrice if they can cut this down to only what is required for the content that appears on the top half of the page (and then client-side fetch the rest after page load).&lt;/p&gt;
&lt;p&gt;If you look up again at the requests up above, row 30 (highlighted in green) is their home banner, the image that is their largest contentful paint. Here it is again:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That thin bar at the start of the request is the connection setup. This occurred because it is the first connection made to this server (this image is hosted on a different domain). FairPrice made an attempt to start this connection setup earlier by using &lt;code&gt;preconnect&lt;/code&gt; and &lt;code&gt;dns-prefetch&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://media.nedigital.sg&quot; /&amp;gt;
&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;https://media.nedigital.sg&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This connection setup could have started earlier if the HTML was received earlier. You see the thin yellow bar at the start of Row 31 and 32? The browser knew about these requests too but it is also waiting for the same connection setup to start downloading the other images. If they cut down the time it takes for the HTML to download, then these image potentially render earlier.&lt;/p&gt;
&lt;p&gt;Another method to avoid this connection setup is to move the largest contentful paint to the same server. Either technique can improve their LCP score (which is right now their lowest mark in their &lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fwww.fairprice.com.sg%2F&amp;amp;tab=desktop&quot;&gt;Core Web Vital audit&lt;/a&gt;).&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Profiling the layout shift&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Above is 2 frames that occurred when the layout shift happened. We can see the font loading and the lazy loading of products (in the bottom-right corner) is causing the layout shift and resulting in the low CLS score.&lt;/p&gt;
&lt;p&gt;By examining the frames of your page loading, you could understand more of the experience your user is facing and find any unexpected layout shifts. Reducing the layout shift will improve your audit score. WebPageTest is a great tool to grab these frames and highlight where the layout shift occurs.&lt;/p&gt;
&lt;h3&gt;Sephora&lt;/h3&gt;
&lt;p&gt;URL: &lt;a href=&quot;https://www.sephora.sg/&quot;&gt;https://www.sephora.sg/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Two areas we will look at for Sephora is their layout shift and the delay in the largest contentful paint. Let&apos;s start with the waterfall request:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Reduce render-blocking font files&lt;/h4&gt;
&lt;p&gt;9 font files were loaded as highest priority from the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; which is render-blocking. Sephora has split the fonts by weight and style into separate font files. If we loop through all the elements on the page and list down their font usages (using some quick &lt;a href=&quot;https://gist.github.com/petermekhaeil/f03702e4df759c0c3d303bde90975fa9&quot;&gt;JavaScript&lt;/a&gt;) we can see the usages of these 9 font files:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The most fonts used are: &quot;Sephora Sans 700&quot; (83% of the page) and &quot;Sephora Sans 400&quot; (13% of the page). Avalon isn&apos;t loaded on this page but 2 render-blocking assets for this font is loaded.&lt;/p&gt;
&lt;p&gt;If Sephora is certain they need this many fonts for the landing page, it will come with a heavy cost of delayed page load. If the Sephora brand is okay with an alternative - they can swap out the other 58 elements (4% of the page) to use the same fonts. They can then remove 7 font files that was holding up page rendering.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Remove unused JavaScript&lt;/h4&gt;
&lt;p&gt;We can use Chrome Dev Tools to see how much of that JavaScript is being used on the first page:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;1.3 MB (54%) of JavaScript was not used on the page load. Let&apos;s look at the requests again:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Because the JavaScript resources are render-blocking, the rest of the page requests such as images and SVGs didn&apos;t load until the JavaScript finished downloading. If we can reduce the size of the JavaScript bundles, the rest of the page can continue rendering earlier.&lt;/p&gt;
&lt;p&gt;Alternatively, we can &lt;code&gt;async&lt;/code&gt; or &lt;code&gt;defer&lt;/code&gt; the scripts if they are not required to render the first paint. These attributes will tell the browser it can continue rendering the HTML while it loads these scripts in the background. Here is a great article on &lt;a href=&quot;https://web.dev/efficiently-load-third-party-javascript/&quot;&gt;Efficiently load third-party JavaScript&lt;/a&gt; that details how these attributes work.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Preconnect the hero banner&lt;/h4&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The main image is highlighted in green (Row 46). That thin line at the start of the request is the connection setup, which costed 400ms. This happened because of the new domain request (It looks like it sits behind an image optimization server). Sephora can shave some time off the request if they moved it to the same domain, or preconnect to this other domain like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link
  rel=&quot;preconnect&quot;
  href=&quot;https://image-optimizer-reg.production.sephora-asia.net&quot; /&amp;gt;
&amp;lt;link
  rel=&quot;dns-prefetch&quot;
  href=&quot;https://image-optimizer-reg.production.sephora-asia.net&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would allow the connection setup to happen earlier while the browser was working on other parts of the rendering.&lt;/p&gt;
&lt;h3&gt;Circles.Life&lt;/h3&gt;
&lt;p&gt;URL: &lt;a href=&quot;https://www.circles.life&quot;&gt;https://www.circles.life&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This one was fun to examine - we can see in the video strip above that the areas we should tackle is the layout shift and the background image.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Optimise images for the web&lt;/h4&gt;
&lt;p&gt;Let&apos;s look at the request waterfall:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This section of the requests stood out the most. The images on this page can do with some optimization. 21.5 MB of images was loaded on the landing page. If you&apos;re looking for the right tool, &lt;a href=&quot;https://squoosh.app/&quot;&gt;squoosh.app&lt;/a&gt; is the way to go! Let&apos;s try it on the main image that appear above-the-fold:&lt;/p&gt;
&lt;p&gt;Original: 300kb
&lt;/p&gt;
&lt;p&gt;Optimised: 150kb (50%)
&lt;/p&gt;
&lt;p&gt;I&apos;m not a designer so the optimised image might not be ideal to the designer of this image, but they can tweak the settings to find an acceptable level of quality for this image.&lt;/p&gt;
&lt;p&gt;This was the only image visible to the user on page load (above-the-fold). The other 20MB worth of images are found below-the-fold or hidden from the user entirely. This means Circles.Life can defer these off-screen images until they are in the viewport. This can be done using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes&quot;&gt;loading=&quot;lazy&quot;&lt;/a&gt; on the &lt;code&gt;&amp;lt;img /&amp;gt;&lt;/code&gt;. Circles.Life can also use a &lt;a href=&quot;https://wordpress.org/plugins/search/lazy+load/&quot;&gt;lazy-load WordPress plugin&lt;/a&gt; too.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Reduce render-blocking scripts and stylesheets&lt;/h4&gt;
&lt;p&gt;A &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag that is in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; without a &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; attribute is a render-blocking script. This is mostly the resources in the waterfall request that appears before the first render.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;If any of these scripts are not critical code for first render, they should be marked with &lt;code&gt;async&lt;/code&gt; or &lt;code&gt;defer&lt;/code&gt; attributes. Any code not used on this page should be removed. If they are required and are located on a different server, we can prepare the browser for what is coming with &lt;code&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Same goes with stylesheets - if a &lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot;&amp;gt;&lt;/code&gt; does not have &lt;code&gt;disabled&lt;/code&gt; or &lt;code&gt;media&lt;/code&gt; attribute that matches the user&apos;s device, it is considered render-blocking. Identify the critical styling required for first render and keep it in the &lt;code&gt;head&lt;/code&gt;. Then load the rest afterwards.&lt;/p&gt;
&lt;h4&gt;Possible Improvement - Avoid render blocking fonts&lt;/h4&gt;
&lt;p&gt;The site loads a handful of Google Fonts that we can optimise. We can start by pre-connecting the connection setup to Google. This tells the browser to prepare for the download of the font before it is requested:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.gstatic.com&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A stylesheet is required to load the Google fonts. We can avoid it from being render-blocking by switching &lt;code&gt;rel=&quot;stylesheet&quot;&lt;/code&gt; to &lt;code&gt;rel=&quot;preload&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link
  rel=&quot;preload&quot;
  as=&quot;style&quot;
  href=&quot;https://fonts.googleapis.com/css2?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This technique can be used with any fonts. Harry goes in depth in &lt;a href=&quot;https://csswizardry.com/2020/05/the-fastest-google-fonts&quot;&gt;The Fastest Google Fonts&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;The Core Web Vitals aim to measure user experience. The metrics are insightful and improving the scores will have a great result on your users. Pairing the information &lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/&quot;&gt;Page Speed Insights&lt;/a&gt; provides with the performance test results from &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt;, developers have the data they need to analyse possible improvements they can make to their websites.&lt;/p&gt;
&lt;p&gt;By learning from the websites above, the possible improvements that we found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Preconnect early&lt;/li&gt;
&lt;li&gt;Optimise images for the web&lt;/li&gt;
&lt;li&gt;Reduce HTML Size&lt;/li&gt;
&lt;li&gt;Reduce render-blocking font files, scripts and stylesheets&lt;/li&gt;
&lt;li&gt;Remove unused JavaScript and CSS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;I want to know more!&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-webfont-loading/&quot;&gt;Optimize WebFont loading and rendering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/&quot;&gt;Preload: What Is It Good For?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/uses-rel-preconnect/&quot;&gt;Preconnect to required origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/coverage/&quot;&gt;Find Unused JavaScript And CSS With The Coverage Tab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API&quot;&gt;CSS Font Loading API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2021/f1-perf-part-1/&quot;&gt;Who has the fastest F1 website in 2021? Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Thank You&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/&quot;&gt;Jake Archibald&lt;/a&gt; for his amazing lessons on performance testing websites.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/treosh/lighthouse-plugin-field-performance&quot;&gt;Treo.sh&lt;/a&gt; for the scoring technique.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Proxying Ackee through Netlify</title><link>https://petermekhaeil.com/proxying-ackee-through-netlify/</link><guid isPermaLink="true">https://petermekhaeil.com/proxying-ackee-through-netlify/</guid><description>Ackee is an open-source analytics tool that can be self-hosted on your own server. Learn how to use Netlify Redirects to proxy requests to your Ackee server.</description><pubDate>Wed, 29 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Proxying the requests to &lt;a href=&quot;https://ackee.electerious.com/&quot;&gt;Ackee&lt;/a&gt; through Netlify comes with some advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It bypasses the need to configure CORS headers&lt;/strong&gt; - Because Ackee is self-hosted on a domain that is different to your website, during the installation you will be asked to configure CORS headers for Ackee to accept requests from other domains. With proxy rewrites, the browser will be making a request to Ackee through the same domain so we can skip configuring CORS headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It makes it difficult for ad-blockers to detect the tracker&lt;/strong&gt; - We can rewrite the path to the tracker to not include keywords that can be blocked.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.netlify.com/routing/redirects/&quot;&gt;Netlify Redirects&lt;/a&gt; can redirect requests to external services and this is how we will be setting up our proxy rewrites.&lt;/p&gt;
&lt;h2&gt;Adding the rewrite rules&lt;/h2&gt;
&lt;p&gt;Netlify Redirects rules are configured in a file called &lt;code&gt;_redirects&lt;/code&gt;. Create the file if you do not have one already. Here are the rules to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/script.js https://your-ackee-domain.com/tracker.js 200
/api https://your-ackee-domain.com/api 200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we have done is proxying these paths to an external path on another domain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://yourdomain.com/script.js -&amp;gt; https://your-ackee-domain.com/tracker.js
https://yourdomain.com/api -&amp;gt; https://your-ackee-domain.com/api
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Update the path in your script tag&lt;/h2&gt;
&lt;p&gt;You will need to update the Ackee script to reference the new path:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script
  async
  src=&quot;/script.js&quot;
  data-ackee-server=&quot;https://yourdomain.com&quot;
  data-ackee-domain-id=&quot;0000-0000-0000-0000-0000&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Verify the changes&lt;/h2&gt;
&lt;p&gt;Verify the proxy is working by seeing your website making requests to &lt;code&gt;/script.js&lt;/code&gt; and &lt;code&gt;/api&lt;/code&gt; and that they respond with status 200.&lt;/p&gt;
</content:encoded></item><item><title>Improving Shopify page performance using Next.js</title><link>https://petermekhaeil.com/shopify-with-nextjs/</link><guid isPermaLink="true">https://petermekhaeil.com/shopify-with-nextjs/</guid><description>Learn about how Shopify-enabled online stores can improve their web page speed performance by moving across to Next.js. We will use Core Web Vitals to measure the performance that Next.js provides out of the box.</description><pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With Google &lt;a href=&quot;https://support.google.com/webmasters/thread/86521401?hl=en&quot;&gt;announcing&lt;/a&gt; that from May 2021 onwards websites will have their rankings impacted by their &lt;a href=&quot;https://web.dev/learn-web-vitals/&quot;&gt;Core Web Vitals Audit&lt;/a&gt; scores, this is a perfect time for a Shopify-based online store to take the lead against their competitors by improving their SEO and their page performance speeds.&lt;/p&gt;
&lt;p&gt;One way to improve page speed performance is to migrate to a JAMStack architecture - static sites have the benefits of a strong SEO performance boost which is key to an online e-commerce store.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://gobare.com.au/&quot;&gt;Go Bare&lt;/a&gt; is a popular Australian based skin care company hosted on Shopify. They have plans to take on the world globally and so they are going to need a website with strong SEO and fast page load speeds to improve their Google ranking.&lt;/p&gt;
&lt;p&gt;We will use Lighthouse to measure the &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;Lab Data&lt;/a&gt; (because the Chrome User Experience Report &lt;a href=&quot;https://developers.google.com/speed/docs/insights/about#faq&quot;&gt;does not have sufficient real-world speed data&lt;/a&gt; for the new website we are building). Here is the score for the existing website hosted on Shopify:
&lt;/p&gt;
&lt;p&gt;Areas of improvement include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removing unused CSS and JavaScript&lt;/li&gt;
&lt;li&gt;Eliminate render-blocking resources&lt;/li&gt;
&lt;li&gt;Reducing the impact of third-party code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are very common to see on e-commerce websites that allow their owners to purchase custom made UI themes and widgets that do not consider performance in their designs. We are going to look at how we can improve these areas by migrating to a static website designed with performance in mind.&lt;/p&gt;
&lt;h2&gt;Improving the performance scores&lt;/h2&gt;
&lt;p&gt;We are going to use &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; and their &lt;a href=&quot;https://nextjs.org/commerce&quot;&gt;Commerce Framework&lt;/a&gt;. Out of the box, it has a slick look-and-feel and includes the UI components we need without having to build much of it from scratch.&lt;/p&gt;
&lt;p&gt;At the time of migrating to the new website, the Next.js Commerce framework was relatively new and so a &lt;a href=&quot;https://github.com/petermekhaeil/nextjs-commerce-shopify&quot;&gt;Shopify Provider&lt;/a&gt; was required for the integration with the Shopify APIs.&lt;/p&gt;
&lt;p&gt;Shopify provides developers access to their APIs - which will allow us to hook our own Front-End UI to the Shopify backend. These APIs allow us to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a cart on entry to the site&lt;/li&gt;
&lt;li&gt;Add products to the cart&lt;/li&gt;
&lt;li&gt;Checkout by redirecting the user to the Shopify payment page (which is entirely hosted on Shopify&apos;s end).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Shopify redirecting to their own payment page is an awesome feature to the Shopify APIs because it allows us developers to not worry about building payment pages and the security involved in building one.&lt;/p&gt;
&lt;p&gt;Along the way, some changes were made to the UI to improve the lighthouse scores in terms of accessbility and best practices.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;It speaks for itself. Here is the final Lab Data scores after migrating the Front-End stack to a static website:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Next steps for GoBare is to start reporting on real user&apos;s scores and wait for &lt;a href=&quot;https://developers.google.com/web/tools/chrome-user-experience-report&quot;&gt;Chrome UX Report&lt;/a&gt; to have enough Field Data for us to revisit the site&apos;s performance.&lt;/p&gt;
&lt;p&gt;You can check out the website at &lt;a href=&quot;https://gobare.vercel.app/&quot;&gt;gobare.vercel.app&lt;/a&gt; and compare the experience to the original website at &lt;a href=&quot;https://gobare.com.au&quot;&gt;gobare.com.au&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How did Next.js improve the performance?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pre-rendering&lt;/strong&gt; - Each page is generated in advance during the build process. The output is static HTML files with minimal client-side JavaScript to render the pages. The static files are then cached into a CDN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image Optimization&lt;/strong&gt; - Next.js optimizes images on demand and works with images hosted on external sources (in our use-case, they are hosted on Shopify). They are also lazy-loaded and are only loaded once they are in the user&apos;s view.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Splitting&lt;/strong&gt; - Each page will be code split into its own bundle during the build process and will only include what is only required for that page to render. Smaller bundles means less code to execute, which will result in quicker page load speeds. This also includes the CSS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These 3 key items helped Go Bare improve their performance scores - removed unused CSS and Javascript, eliminated render-blocking resources and reduced the impact on third-party code.&lt;/p&gt;
</content:encoded></item><item><title>Adding Dark Mode to your Tailwind CSS website</title><link>https://petermekhaeil.com/tailwind-css-dark-mode/</link><guid isPermaLink="true">https://petermekhaeil.com/tailwind-css-dark-mode/</guid><description>How to add Dark Mode to your Tailwind CSS website.</description><pubDate>Mon, 30 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tailwind CSS v2.0 introduces &lt;a href=&quot;https://tailwindcss.com/docs/dark-mode&quot;&gt;Dark mode support&lt;/a&gt; and with minimal JS and inline SVG, you can allow your users to manually toggle Dark Mode. We are going to walk through what is required to build the same one from &lt;a href=&quot;https://petermekhaeil.com&quot;&gt;petermekhaeil.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Set &lt;code&gt;darkMode&lt;/code&gt; to &lt;code&gt;class&lt;/code&gt; in your config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tailwind.config.js
module.exports = {
  darkMode: &apos;class&apos;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we want to do is toggle the &lt;code&gt;dark&lt;/code&gt; class on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element. When this class appears on your tree, any child elements below it with the &lt;code&gt;dark:&lt;/code&gt; will be applied.&lt;/p&gt;
&lt;p&gt;Let&apos;s start with a base HTML page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body class=&quot;bg-white text-black dark:bg-black dark:text-white&quot;&amp;gt;
    &amp;lt;h1&amp;gt;My Website&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s add the UI switch using SVGs from &lt;a href=&quot;https://heroicons.com/&quot;&gt;Heroicons&lt;/a&gt;. We will be using the Moon and Sun SVGs. They also have IDs attached &lt;code&gt;#toggle-dark&lt;/code&gt; and &lt;code&gt;#toggle-light&lt;/code&gt;. You&apos;ll also noticed they are hidden using the &lt;code&gt;hidden&lt;/code&gt; class. This is because we would like to avoid any flicker while the app decides which SVG to show (more on this later).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body class=&quot;bg-white text-black dark:bg-black dark:text-white&quot;&amp;gt;
    &amp;lt;h1&amp;gt;My Website&amp;lt;/h1&amp;gt;
    &amp;lt;!-- Moon SVG from Heroicons --&amp;gt;
    &amp;lt;svg
      xmlns=&quot;http://www.w3.org/2000/svg&quot;
      fill=&quot;none&quot;
      viewBox=&quot;0 0 24 24&quot;
      stroke=&quot;currentColor&quot;
      class=&quot;hidden h-6 w-6 cursor-pointer&quot;
      id=&quot;toggle-dark&quot;&amp;gt;
      &amp;lt;path
        stroke-linecap=&quot;round&quot;
        stroke-linejoin=&quot;round&quot;
        stroke-width=&quot;2&quot;
        d=&quot;M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z&quot; /&amp;gt;
    &amp;lt;/svg&amp;gt;
    &amp;lt;!-- Sun SVG from Heroicons --&amp;gt;
    &amp;lt;svg
      xmlns=&quot;http://www.w3.org/2000/svg&quot;
      fill=&quot;none&quot;
      viewBox=&quot;0 0 24 24&quot;
      stroke=&quot;currentColor&quot;
      class=&quot;hidden h-6 w-6 cursor-pointer&quot;
      id=&quot;toggle-light&quot;&amp;gt;
      &amp;lt;path
        stroke-linecap=&quot;round&quot;
        stroke-linejoin=&quot;round&quot;
        stroke-width=&quot;2&quot;
        d=&quot;M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z&quot; /&amp;gt;
    &amp;lt;/svg&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will check if the user has dark mode enabled in their browser. If enabled, then toggle the correct SVG to appear and add the &lt;code&gt;dark&lt;/code&gt; class to &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var userPrefersDark = window.matchMedia(&apos;(prefers-color-scheme: dark)&apos;);
var toggleDark = document.getElementById(&apos;toggle-dark&apos;);
var toggleLight = document.getElementById(&apos;toggle-light&apos;);
var htmlElem = document.querySelector(&apos;html&apos;);

if (userPrefersDark.matches)) {
  htmlElem.classList.add(&apos;dark&apos;);
  toggleDark.classList.add(&apos;visible&apos;);
  toggleLight.classList.remove(&apos;hidden&apos;);
} else {
  toggleLight.classList.add(&apos;visible&apos;);
  toggleDark.classList.remove(&apos;hidden&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s now add some event handlers to the SVGs to toggle dark mode on/off:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;toggleLight.addEventListener(&apos;click&apos;, function () {
  localStorage.setItem(&apos;theme&apos;, &apos;light&apos;);
  htmlElem.classList.remove(&apos;dark&apos;);
  toggleDark.classList.add(&apos;visible&apos;);
  toggleDark.classList.remove(&apos;hidden&apos;);
  toggleLight.classList.add(&apos;hidden&apos;);
  toggleLight.classList.remove(&apos;visible&apos;);
});

toggleDark.addEventListener(&apos;click&apos;, function () {
  localStorage.setItem(&apos;theme&apos;, &apos;dark&apos;);
  htmlElem.classList.add(&apos;dark&apos;);
  toggleLight.classList.add(&apos;visible&apos;);
  toggleLight.classList.remove(&apos;hidden&apos;);
  toggleDark.classList.add(&apos;hidden&apos;);
  toggleDark.classList.remove(&apos;visible&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One last thing we need to do is store the users preference - we want to do this otherwise when they navigate from one page to another, the setting will reset. We will use &lt;code&gt;localStorage&lt;/code&gt; to store the &lt;code&gt;theme&lt;/code&gt;. Replace the above JS blocks with this final JS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var theme = localStorage.getItem(&apos;theme&apos;);
var userPrefersDark = window.matchMedia(&apos;(prefers-color-scheme: dark)&apos;);
var toggleDark = document.getElementById(&apos;toggle-dark&apos;);
var toggleLight = document.getElementById(&apos;toggle-light&apos;);
var htmlElem = document.querySelector(&apos;html&apos;);

if (theme === &apos;dark&apos; || (!theme &amp;amp;&amp;amp; userPrefersDark.matches)) {
  htmlElem.classList.add(&apos;dark&apos;);
  toggleDark.classList.add(&apos;visible&apos;);
  toggleLight.classList.remove(&apos;hidden&apos;);
} else {
  toggleLight.classList.add(&apos;visible&apos;);
  toggleDark.classList.remove(&apos;hidden&apos;);
}

toggleLight.addEventListener(&apos;click&apos;, function () {
  localStorage.setItem(&apos;theme&apos;, &apos;light&apos;);
  htmlElem.classList.remove(&apos;dark&apos;);
  toggleDark.classList.add(&apos;visible&apos;);
  toggleDark.classList.remove(&apos;hidden&apos;);
  toggleLight.classList.add(&apos;hidden&apos;);
  toggleLight.classList.remove(&apos;visible&apos;);
});

toggleDark.addEventListener(&apos;click&apos;, function () {
  localStorage.setItem(&apos;theme&apos;, &apos;dark&apos;);
  htmlElem.classList.add(&apos;dark&apos;);
  toggleLight.classList.add(&apos;visible&apos;);
  toggleLight.classList.remove(&apos;hidden&apos;);
  toggleDark.classList.add(&apos;hidden&apos;);
  toggleDark.classList.remove(&apos;visible&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can view the &lt;a href=&quot;https://github.com/petermekhaeil/petermekhaeil.com/blob/master/src/_includes/base.njk&quot;&gt;source code&lt;/a&gt; to see it all put together.&lt;/p&gt;
&lt;h3&gt;@tailwindcss/typography&lt;/h3&gt;
&lt;p&gt;If you use &lt;a href=&quot;https://tailwindcss.com/docs/typography-plugin&quot;&gt;@tailwindcss/typography&lt;/a&gt;, you&apos;ll need to add some configs to get dark mode working.&lt;/p&gt;
&lt;p&gt;Add a &lt;code&gt;dark:prose-dark&lt;/code&gt; class. This will only work when placed next to &lt;code&gt;prose&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;article class=&quot;prose dark:prose-dark&quot;&amp;gt;&amp;lt;/article&amp;gt;
  &amp;lt;h1&amp;gt;Cannot believe adding Dark Mode is this simple!&amp;lt;/h1&amp;gt;
&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Extend your configuration to add a dark &lt;a href=&quot;https://tailwindcss.com/docs/theme&quot;&gt;theme&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tailwind.config.js
module.exports = {
  darkMode: &apos;class&apos;
  theme: {
    extend: {
      typography: (theme) =&amp;gt; ({
        DEFAULT: {
          css: {
            // ...
          }
        },
        dark: {
          css: {
            color: theme(&apos;colors.gray.200&apos;)
          }
        }
      })
    }
  },
  variants: {
    typography: [&apos;responsive&apos;, &apos;dark&apos;]
  },
  plugins: [require(&apos;@tailwindcss/typography&apos;)]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;ve previously extended your theme for Tailwind CSS 1.x, for Tailwind CSS 2.0 you&apos;ll need to &lt;a href=&quot;https://tailwindcss.com/docs/upgrading-to-v2#update-default-theme-keys-to-default&quot;&gt;update default theme key to DEFAULT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will then need to extend the rest of your styles. &lt;a href=&quot;https://github.com/petermekhaeil/petermekhaeil.com/blob/master/tailwind.config.js&quot;&gt;Use this blog&apos;s config as an example&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Typescript tips by Matt Pocock</title><link>https://petermekhaeil.com/typescript-tips-by-matt-pocock/</link><guid isPermaLink="true">https://petermekhaeil.com/typescript-tips-by-matt-pocock/</guid><description>My notes on Matt Pocock&apos;s videos on TypeScript tips.</description><pubDate>Thu, 01 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Tweet } from &apos;astro-embed&apos;;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/mattpocockuk&quot;&gt;Matt Pocock&lt;/a&gt; posted a collection of videos on TypeScripts tips that I recommend everyone to watch. These are my notes of each tip that he shared.&lt;/p&gt;
&lt;h3&gt;Tip #1: Derive a union type from an object&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1497262298368409605&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Derive a union type from an object using dynamic keys. No need to create a union type and specifying each object key manually.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export const fruitCounts = {
  apple: 1,
  pear: 4,
  banana: 26
};

type FruitCounts = typeof fruitCounts;

type SingleFruitCount = {
  [K in keyof FruitCounts]: {
    [K2 in K]: number;
  };
}[keyof FruitCounts];

const singleFruitCount: SingleFruitCount = {
  apple: 2
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #2: Transform a union to another union using the &lt;code&gt;in&lt;/code&gt; operator&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1498284926621396992&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Transform &lt;code&gt;{ type: &apos;user&apos; }&lt;/code&gt; to &lt;code&gt;{ type: &apos;user&apos;, userId: string }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Record&lt;/code&gt; supports template literal types.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export type Entity =
  | {
      type: &apos;user&apos;;
    }
  | {
      type: &apos;post&apos;;
    }
  | {
      type: &apos;comment&apos;;
    };

type EntityWithId = {
  [EntityType in Entity[&apos;type&apos;]]: {
    type: EntityType;
  } &amp;amp; Record&amp;lt;`${EntityType}Id`, string&amp;gt;;
}[Entity[&apos;type&apos;]];

const resultComment: EntityWithId = {
  type: &apos;comment&apos;,
  commentId: &apos;123&apos;
};

const resultPost: EntityWithId = {
  type: &apos;post&apos;,
  postId: &apos;123&apos;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #3: String interpolation&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1499002040168636420&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgZRlYA7A5gGjgVQ2AgzgF84AzKCEOAIhgGcBaGCCAGwCMBTTmPQDcAKBEBjEk3gBHAK68oATzgBeOAAMA9AAtavAPwBDVZQ4AybqoDuEaxtEiYSsLzgBFBcrVxnriJRw8opKjn5uyLySGAAmniEACkawPqjo2AB0yGCcwDAAPPHKePQG9AB8ANoAjAC6YS5uRUoAopy8ILwYzKlomFhZOXn5kdFxXkpJsCXmFQ2uHhNTRiBMPggicHCVzW0dXfCYiyF7nd1MlRhyIHxQtbUAXIibW9sA0rwqR2n9g7kFu3aZxgJVUFUqAAZ7k8fplsv98i9XsdlKcDjgka96GDMVU6qItmRRGQdhM0edLtdbvdHNFpHAINwAFZPQjEDAZACyiiwvEKS2SKyY5XWLyMT3oZgg9AxW24Ets1hlIiJIiAA&quot;&gt;TS Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Uses &lt;a href=&quot;https://github.com/millsp/ts-toolbelt&quot;&gt;ts-toolbelt&lt;/a&gt; to split strings and merge objects.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { String, Union } from &apos;ts-toolbelt&apos;;

const query = `/home?a=foo&amp;amp;b=wow`;

type Query = typeof query;

type SecondQueryPart = String.Split&amp;lt;Query, &apos;?&apos;&amp;gt;[1];

type QueryElements = String.Split&amp;lt;SecondQueryPart, &apos;&amp;amp;&apos;&amp;gt;;

type QueryParams = {
  [QueryElement in QueryElements[number]]: {
    [Key in String.Split&amp;lt;QueryElement, &apos;=&apos;&amp;gt;[0]]: String.Split&amp;lt;
      QueryElement,
      &apos;=&apos;
    &amp;gt;[1];
  };
}[QueryElements[number]];

const obj: Union.Merge&amp;lt;QueryParams&amp;gt; = {
  a: &apos;foo&apos;,
  b: &apos;wow&apos;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #4: Function overloads&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1499730377337827336&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABBOBbADnAzgUwDwCSY6IUANIgGIwBOWUAgjQOYB8AFAFCKKiQBcidjGKlBRElACUiALysqtek2acpg4aKjitM+YrqMWAbk6c+0eEhQZs+CaQrVDKigGUcKMABMVHbrzgEBoikjqSegrOyixkARYATCFagtFGzJGIHl6+LGrJYYgO0nIK2Qi5zKbmQbAIyGiYuIRaTkrp7p4VrogAKgAWtJX+PBYFYkW6pQYxzHGjQUlCoRNpKpnlPq7xQQDM49pZXVssmQNDKvnLKZMR0+c0ldUWddaNduwAdN8AhixYgh+YAAngBtAC6MgA3gEAPSwooYAA2OFQODAUEQSJ+UBwNACNBwUBANCQUIAvogflgqSDTOSzF56FTvN4APJgHByIQ-QRgECoABGeMyMJ4hOJpKpiAA1IgAIz06pMzH81C9OBuKA0ETMbnsXmINXCmiiglEklIH6fKCa7W69hSJWMhDM+g6sDMDUAOQF+sN7t1ZvFFql6D+uCIUANTs45Oq8L6gxpAHcYEikYgU3AaABrRDCiA-EC4RBQfpciWWsvA9A4GmoHEQfpliuIFaYqC1nCcRNwYCtrmcgAemJeVk+nBVLPZnI1Wo9etkDVsuANrI5OAoavn9s9FEDnp9AtjvYR51T6cz3rZvSzOfzheLparUq7dcQ-cQAAMd3bF9+iDeHA9ZnkacCYo2UDNoO7ZaDWH5ft+Pwbpy36TtOh5enAvpCni3I2E0ODsH+C66hQKGzjgsZAA&quot;&gt;TS Playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add type signatures to a compose function.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function compose&amp;lt;Input, FirstArg&amp;gt;(
  func: (input: Input) =&amp;gt; FirstArg
): (input: Input) =&amp;gt; FirstArg;

function compose&amp;lt;Input, FirstArg, SecondArg&amp;gt;(
  func: (input: Input) =&amp;gt; FirstArg,
  func2: (input: FirstArg) =&amp;gt; SecondArg
): (input: Input) =&amp;gt; SecondArg;

function compose&amp;lt;Input, FirstArg, SecondArg, ThirdArg&amp;gt;(
  func: (input: Input) =&amp;gt; FirstArg,
  func2: (input: FirstArg) =&amp;gt; SecondArg,
  func3: (input: SecondArg) =&amp;gt; ThirdArg
): (input: Input) =&amp;gt; ThirdArg;

function compose(...args: any[]) {
  // Implement later
  return {} as any;
}

const addOne = (a: number) =&amp;gt; {
  return a + 1;
};

const numToString = (a: number) =&amp;gt; {
  return a.toString();
};

const stringToNum = (a: string) =&amp;gt; {
  return parseInt(a);
};

// This will work because the return types match the input type
// of the next function.
const addOneToString = compose(addOne, numToString, stringToNum);

// This will NOT work because return type of `numToString` does
// not match the input type of `addOne`.
const stringToNumber = compose(numToString, addOne);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #5: Using &lt;code&gt;extends&lt;/code&gt; to narrow the value of a generic&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1500813765973053440&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Narrow the value of a generic using &lt;code&gt;extends&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export const getDeepValue = &amp;lt;
  Obj,
  FirstKey extends keyof Obj,
  SecondKey extends keyof Obj[FirstKey]
&amp;gt;(
  obj: Obj,
  firstKey: FirstKey,
  secondKey: SecondKey
): Obj[FirstKey][SecondKey] =&amp;gt; {
  return {} as any;
};

const obj = {
  foo: {
    a: true,
    b: 2
  },
  bar: {
    c: &apos;cool&apos;,
    d: 2
  }
};

const result = getDeepValue(obj, &apos;bar&apos;, &apos;c&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #6: Extract types using &lt;code&gt;infer&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1501533441791193090&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Extract props from React components using &lt;code&gt;infer&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;

const MyComponent = (props: { enabled: boolean }) =&amp;gt; {
  return null;
};

class MyOtherComponent extends React.Component&amp;lt;{ enabled: boolean }&amp;gt; {}

type PropsFrom&amp;lt;TComponent&amp;gt; =
  TComponent extends React.FC&amp;lt;infer Props&amp;gt;
    ? Props
    : TComponent extends React.ComponentClass&amp;lt;infer Props&amp;gt;
      ? Props
      : never;

const props: PropsFrom&amp;lt;typeof MyComponent&amp;gt; = {
  enabled: true
};

const otherProps: PropsFrom&amp;lt;typeof MyOtherComponent&amp;gt; = {
  enabled: true
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #7: Using generics and &lt;code&gt;keyof&lt;/code&gt; to type &lt;code&gt;Object.keys&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1502264005251018754&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const myObject = {
  a: 1,
  b: 2,
  c: 3
};

const objectKeys = &amp;lt;Obj&amp;gt;(obj: Obj): (keyof Obj)[] =&amp;gt; {
  return Object.keys(obj) as (keyof Obj)[];
};

objectKeys(myObject).forEach((key) =&amp;gt; {
  console.log(myObject[key]);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #8: Using generics in React props&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1503352924537339904&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface TableProps&amp;lt;TItem&amp;gt; {
  items: TItem[];
  renderItem: (item: TItem) =&amp;gt; React.ReactNode;
}

export function Table&amp;lt;TItem&amp;gt;(props: TableProps&amp;lt;TItem&amp;gt;) {
  return null;
}

const Component = () =&amp;gt; {
  return (
    &amp;lt;Table
      items={[
        {
          id: &apos;1&apos;,
          name: &apos;Peter&apos;
        }
      ]}
      renderItem={(item) =&amp;gt; &amp;lt;div&amp;gt;{item.name}&amp;lt;/div&amp;gt;}
    /&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #9: Generics can be &apos;curried&apos; through functions&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1504088070869884929&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const makeKeyRemover =
  &amp;lt;Key extends string&amp;gt;(keys: Key[]) =&amp;gt;
  &amp;lt;Obj&amp;gt;(obj: Obj): Omit&amp;lt;Obj, Key&amp;gt; =&amp;gt; {
    return {} as any;
  };

const keyRemover = makeKeyRemover([&apos;a&apos;, &apos;b&apos;]);

const newObject = keyRemover({ a: 1, b: 2, c: 3 });

// Only `c` is available:
newObject.c;
// ^? (property) c: number
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #10: Throw error messages for type checks&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1504802045794078723&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Return an error message as a string type.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;type CheckForBadArgs&amp;lt;Arg&amp;gt; = Arg extends any[]
  ? &apos;You cannot compare two arrays using deepEqualCompare&apos;
  : Arg;

export const deepEqualCompare = &amp;lt;Arg&amp;gt;(
  a: CheckForBadArgs&amp;lt;Arg&amp;gt;,
  b: CheckForBadArgs&amp;lt;Arg&amp;gt;
): boolean =&amp;gt; {
  if (Array.isArray(a) || Array.isArray(b)) {
    throw new Error(&apos;You cannot compare two arrays using deepEqualCompare&apos;);
  }

  return a === b;
};

deepEqualCompare(1, 1);
// ^? const deepEqualCompare: &amp;lt;number&amp;gt;(a: number, b: number) =&amp;gt; boolean

// Below will throw error:
// Argument of type &apos;never[]&apos; is not assignable to parameter
// of type &apos;&quot;You cannot compare two arrays using deepEqualCompare&quot;&apos;.
deepEqualCompare([], []);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #11: Deep partials&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1505892984658743300&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Recursively partial down an object to make each key optional.&lt;/li&gt;
&lt;li&gt;Useful for mocking an entity in a test file.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export type DeepPartial&amp;lt;Thing&amp;gt; = Thing extends Function
  ? Thing
  : Thing extends Array&amp;lt;infer InferredArrayMember&amp;gt;
    ? DeepPartialArray&amp;lt;InferredArrayMember&amp;gt;
    : Thing extends object
      ? DeepPartialObject&amp;lt;Thing&amp;gt;
      : Thing | undefined;

interface DeepPartialArray&amp;lt;Thing&amp;gt; extends Array&amp;lt;DeepPartial&amp;lt;Thing&amp;gt;&amp;gt; {}

type DeepPartialObject&amp;lt;Thing&amp;gt; = {
  [Key in keyof Thing]?: DeepPartial&amp;lt;Thing[Key]&amp;gt;;
};

interface Post {
  id: string;
  comments: { value: string }[];
  meta: {
    name: string;
    description: string;
  };
}

const post: DeepPartial&amp;lt;Post&amp;gt; = {
  id: &apos;1&apos;,
  meta: {
    description: &apos;123&apos;
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #12: Loose autocomplete&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1506607945445949446&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Autocomplete while allowing arbitrary values.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;type IconSize = LooseAutocomplete&amp;lt;&apos;sm&apos; | &apos;xs&apos;&amp;gt;;

type LooseAutocomplete&amp;lt;T extends string&amp;gt; = T | Omit&amp;lt;String, T&amp;gt;;

interface IconProps {
  size: IconSize;
}

export const Icon = (props: IconProps) =&amp;gt; {
  return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;;
};

const Comp1 = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Icon size=&quot;xs&quot;&amp;gt;&amp;lt;/Icon&amp;gt;
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #13: Grab types from modules&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1508408811635322883&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Create type from another module.&lt;/li&gt;
&lt;li&gt;Map over values to create a union.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// constants.ts
export const ADD_TODO = &apos;ADD_TODO&apos;;
export const REMOVE_TODO = &apos;REMOVE_TODO&apos;;
export const EDIT_TODO = &apos;EDIT_TODO&apos;;

// types.ts
export type ActionModule = typeof import(&apos;./constants&apos;);

export type Action = ActionModule[keyof ActionModule];
// ^? &quot;ADD_TODO&quot; | &quot;REMOVE_TODO&quot; | &quot;EDIT_TODO&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #14: Globals in TypeScript&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1509131700382715905&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Globals can cross module boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// types.ts
declare global {
  interface GlobalReducerEvent {}
}

export type GlobalReducer&amp;lt;TState&amp;gt; = {
  state: TState;
  event: {
    [EventType in keyof GlobalReducerEvent]: {
      type: EventType;
    } &amp;amp; GlobalReducerEvent[EventType];
  }[keyof GlobalReducerEvent]
} =&amp;gt; TState;

// todoReducer.ts
import { GlobalReducer } from &apos;./types&apos;;

declare global {
  interface GlobalReducerEvent {
    ADD_TODO: {
      text: string;
    };
  }
}

export const todosReducer: GlobalReducer&amp;lt;{
  todos: { id: string }[]
}&amp;gt; = (state, event) =&amp;gt; {
  return state;
};


// userReducer.ts
import { GlobalReducer } from &apos;./types&apos;;

declare global {
  interface GlobalReducerEvent {
    LOG_IN: {};
  }
}

export const userReducer: GlobalReducer&amp;lt;{ id: string }&amp;gt; = (state, event) =&amp;gt; {
  // GlobalReducer has the globals from across all reducers:
  // event: { type: &apos;LOG_IN&apos;; } | ({ type: &apos;ADD_TODO&apos; } &amp;amp; { text: string })
  return state;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tip #15: Use Generics to dynamically specify the number and types of function arguments&lt;/h3&gt;
&lt;p&gt;&amp;lt;div class=&quot;not-prose&quot;&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/mattpocockuk/status/1509850662795989005&quot; /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;Extract&lt;/code&gt; and &lt;code&gt;infer&lt;/code&gt; to specify if a type is optional or not.&lt;/li&gt;
&lt;li&gt;Give names to &lt;code&gt;args_0&lt;/code&gt; and &lt;code&gt;args_1&lt;/code&gt; using named tuple.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export type Event =
  | { type: &apos;LOG_IN&apos;; payload: { userId: string } }
  | { type: &apos;SIGN_OUT&apos; };

const sendEvent = &amp;lt;Type extends Event[&apos;type&apos;]&amp;gt;(
  ...args: Extract&amp;lt;Event, { type: Type }&amp;gt; extends { payload: infer TPayload }
    ? // Named tuple
      [type: Type, payload: TPayload]
    : [type: Type]
) =&amp;gt; {};

sendEvent(&apos;SIGN_OUT&apos;);
sendEvent(&apos;LOG_IN&apos;, { userId: &apos;123&apos; });
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Using FaunaDB and Netlify Functions for Analytics</title><link>https://petermekhaeil.com/using-fauna-and-netlify-functions-for-analytics/</link><guid isPermaLink="true">https://petermekhaeil.com/using-fauna-and-netlify-functions-for-analytics/</guid><description>Build an API that will allow you to track page hits using FaunaDB and Netlify functions.</description><pubDate>Sun, 06 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I continue to build this blog, I want to add some analytics to get an idea of my viewers and what they are interested in reading. Using &lt;a href=&quot;https://fauna.com/&quot;&gt;FaunaDB&lt;/a&gt; and &lt;a href=&quot;https://www.netlify.com/products/functions/&quot;&gt;Netlify Functions&lt;/a&gt;, we are going to build a basic API that will allow us to track the page hits.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;You can use Netlify Functions to talk to a database such as FaunaDB. In the below example, we use tracking page hits as a use-case to save into the database. You can found the source code to the Netlify Function &lt;a href=&quot;https://github.com/petermekhaeil/petermekhaeil.com/blob/master/functions/tracker.js&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The Setup&lt;/h2&gt;
&lt;h3&gt;Front End&lt;/h3&gt;
&lt;p&gt;We want to keep it as minimal as possible. We can achieve this without using JavaScript by implementing a &quot;Tracking Pixel&quot; which is a &lt;code&gt;&amp;lt;img/&amp;gt;&lt;/code&gt; that will be placed on each page. This approach allows us to talk one-way to the server without expecting any data in return. This image &lt;code&gt;src&lt;/code&gt; will request our Netlify function which will respond with an transparent image. The Netlify Function will store the page hits into the database and then respond to the client with the transparent pixel.&lt;/p&gt;
&lt;h3&gt;Back End&lt;/h3&gt;
&lt;p&gt;This blog doesn&apos;t sit on a server (woohoo!). It&apos;s a static website built on &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; and served from &lt;a href=&quot;https://netlify.app/&quot;&gt;Netlify&lt;/a&gt;. Our solution will involve using &lt;a href=&quot;https://www.netlify.com/products/functions/&quot;&gt;Netlify Functions&lt;/a&gt; which are serverless functions that you can deploy alongside your static website. Behind the scenes, Netlify Functions run on AWS Lambda and the beautiful thing about this is that you don&apos;t need to worry about using AWS. Lambda functions are fully managed and auto-scale. AWS and Netlify will take care of setting everything up for you, all we need to do is write the API function and deploy it.&lt;/p&gt;
&lt;h3&gt;Database&lt;/h3&gt;
&lt;p&gt;We going to use &lt;a href=&quot;https://fauna.com/&quot;&gt;FaunaDB&lt;/a&gt;, a serverless database that is well-documented, can be set up in a couple minutes and works well with serverless functions. This is where we are going to store the page URLs and their page hits.&lt;/p&gt;
&lt;p&gt;Everything together should look like this:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Putting it together&lt;/h2&gt;
&lt;h3&gt;FaunaDB&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dashboard.fauna.com/accounts/register&quot;&gt;Sign up&lt;/a&gt; for an account. The Free account is enough for this tutorial.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Database and name it after your project.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new collection &lt;code&gt;hits&lt;/code&gt;. We will use this to store the page hits.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We now need to create a new &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/indexes?lang=javascript&quot;&gt;Index&lt;/a&gt;. In FaunaDB, you use the index to query your documents. (a) Set the &lt;code&gt;Source Collection&lt;/code&gt; to &lt;code&gt;hits&lt;/code&gt; (b) Set &lt;code&gt;Index Name&lt;/code&gt; to &lt;code&gt;hits_by_pathname&lt;/code&gt; (c) Set &lt;code&gt;Terms&lt;/code&gt; to &lt;code&gt;data.pathname&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate an API key under &lt;code&gt;Security&lt;/code&gt;. We will use this to query our database from within our serverless function.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Netlify Function&lt;/h3&gt;
&lt;p&gt;We need to write an API that will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Query the database for the current page hit for the requested page.&lt;/li&gt;
&lt;li&gt;Increase this count and store it back into the database.&lt;/li&gt;
&lt;li&gt;Respond to the client with a transparent image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We are going to use the &lt;a href=&quot;https://github.com/fauna/faunadb-js&quot;&gt;faunadb&lt;/a&gt; package. We will need this as dependency to the serverless function (it won&apos;t be used on the client side).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install faunadb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a file &lt;code&gt;./netlify/functions/tracker.js&lt;/code&gt;. Below is the code we will use. I&apos;ve added comments to walk through what the function does:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const faunadb = require(&apos;faunadb&apos;);

exports.handler = async (event) =&amp;gt; {
  const { headers } = event;
  const q = faunadb.query;

  // Connect to our database.
  const client = new faunadb.Client({
    // This is the Fauna API Key we created.
    // It&apos;s stored in Netlify Environment Variables.
    secret: process.env.FAUNA_SECRET_KEY
  });

  // We will use the referer to know which page we want to track.
  const referer = headers.referer;
  const { pathname } = new URL(referer);

  try {
    await client.query(
      // Bind variables to be in the expression (second parameter)
      q.Let(
        {
          // Match the document by the pathname index
          match: q.Match(q.Index(&apos;hits_by_pathname&apos;), pathname)
        },
        // Conditionally evaluate expressions
        q.If(
          // IF the document exists
          q.Exists(q.Var(&apos;match&apos;)),
          // THEN update the document with an updated page hit.
          q.Update(q.Select(&apos;ref&apos;, q.Get(q.Var(&apos;match&apos;))), {
            data: {
              hits: q.Add(
                // Increment the previous hits by 1
                q.Select([&apos;data&apos;, &apos;hits&apos;], q.Get(q.Var(&apos;match&apos;))),
                1
              )
            }
          }),
          // ELSE create the document and set the page hit to 1
          q.Create(q.Collection(&apos;hits&apos;), {
            data: { pathname, hits: 1 }
          })
        )
      )
    );
  } catch (error) {
    console.error(error);
  }

  // We respond with a transparent image
  return {
    statusCode: 200,
    body: &apos;R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==&apos;,
    headers: { &apos;content-type&apos;: &apos;image/gif&apos; },
    isBase64Encoded: true
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions&quot;&gt;Fauna Query Language&lt;/a&gt; provides a collection of built-in functions that we used in this function. We started with &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/let?lang=javascript&quot;&gt;Let&lt;/a&gt; that allowed us to create the variable &lt;code&gt;match&lt;/code&gt;. The expression used an &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/if?lang=javascript&quot;&gt;If&lt;/a&gt; which takes 3 parameters &lt;code&gt;( cond_expr, true_expr, false_expr )&lt;/code&gt;. For the condition, we want to check if the document exists using &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/exists?lang=javascript&quot;&gt;Exists&lt;/a&gt;. If it exists, we referenced it using &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/select?lang=javascript&quot;&gt;Select&lt;/a&gt; and &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/update?lang=javascript&quot;&gt;Update&lt;/a&gt;. We used &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/add?lang=javascript&quot;&gt;Add&lt;/a&gt; to add &lt;code&gt;1&lt;/code&gt; to the previous count. If the document did not exist, we created it with &lt;a href=&quot;https://docs.fauna.com/fauna/current/api/fql/functions/create?lang=javascript&quot;&gt;Create&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Build Environment Variables&lt;/h3&gt;
&lt;p&gt;We need to add the FaunaDB API Key into Netlify&apos;s &lt;a href=&quot;https://docs.netlify.com/configure-builds/environment-variables/&quot;&gt;Environment Variables&lt;/a&gt;. It&apos;s referenced in the above code using &lt;code&gt;process.env.FAUNA_SECRET_KEY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Insert the image&lt;/h3&gt;
&lt;p&gt;Place the &lt;code&gt;&amp;lt;img/&amp;gt;&lt;/code&gt; on your template. Here is how I did it for my template:&lt;/p&gt;
&lt;p&gt;{% raw %}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% if env.environment == &quot;production&quot; %}
&amp;lt;img src=&quot;/.netlify/functions/tracker&quot; title=&quot;&quot; /&amp;gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{% endraw %}&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;Testing is a large topic on it&apos;s own. Read up on &lt;a href=&quot;https://docs.netlify.com/cli/get-started/#get-started-with-netlify-dev&quot;&gt;Netlify Dev&lt;/a&gt; to get started on the Netlify CLI which will let you test your serverless function locally. I recommend you to test your functions before committing and deploying.&lt;/p&gt;
&lt;h2&gt;Wrapping it up&lt;/h2&gt;
&lt;p&gt;Serverless has a large collection of use-cases other than connecting to a database - such as sending out emails and processing payments. With Netlify Functions we didn&apos;t need to worry about setting up any cloud infrastructure or configurations, Netlify took care of that for us. FaunaDB being serverless, it also worked very well with our use-case. Both Netlify Functions and FaunaDB have usage-based pricing models, so you only pay for what you consume. Give both a try - you can go one step further and build yourself a portal that queries your new database and graph out your page hits.&lt;/p&gt;
</content:encoded></item><item><title>What I have learnt as an engineering manager</title><link>https://petermekhaeil.com/what-i-learnt-as-an-eng-mgr/</link><guid isPermaLink="true">https://petermekhaeil.com/what-i-learnt-as-an-eng-mgr/</guid><description>A collection of learnings from my adventures as an engineering manager.</description><pubDate>Tue, 24 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Taking care of people is hard.&lt;/strong&gt; Mentors will warn you about this, but you’ll only really know how hard it is when you experience it. Regardless of the difficulty, it’s very fulfilling. There is a strong sense of satisfaction in your duties. And that is because people’s life’s and careers were involved and impacted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You’re either in it 100% or you’re not in it at all.&lt;/strong&gt; You cannot do half-baked job taking care of people and their careers. Their career is their top priority so it should be yours too. Model your own roles and responsibilities so it balances people first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You will have very difficult conversations.&lt;/strong&gt; Like really &lt;em&gt;difficult&lt;/em&gt; ones. As a manager, you will need to confront them and you will learn how to handle them. Do not avoid giving critical feedback because people need to hear your feedback for their own self-growth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Feedback is critical.&lt;/strong&gt; There is different types of feedback that you can provide. You can use the &lt;a href=&quot;https://www.ccl.org/articles/leading-effectively-articles/hr-pipeline-a-quick-win-to-improve-your-talent-development-process/&quot;&gt;Situation-Behavior-Impact (SBI) Feedback Model&lt;/a&gt; to provide constructive feedback to your peers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don’t be afraid to get personal in your 1:1s.&lt;/strong&gt; Start the conversations as you would when you go for coffee with them. Ask them how is life treating them, or what they have planned for the weekend. And remember the conversations you’ve had so you can continue the flow in the next 1:1s. Sometimes these sessions shouldn&apos;t be about work and perhaps your team member wants to talk or take a break from the work hustle. This is how relationships are built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It&apos;s not a solo job.&lt;/strong&gt; Look out for the team members that will help you build and support the team. Do not think that you need to do everything yourself. A successful team is one that functions without you getting involved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&quot;Let it go&quot;.&lt;/strong&gt; Wish someone told me these words during my first few months. The transition from an individual contributor to people manager isn’t straight forward. You’ll naturally want to write code and built features because you were doing it for so many years. It&apos;s part of your blood. You&apos;ll now need to remember you&apos;ve changed profession. Plan your delegation. Your hands will be tied up with a lot of new responsibilities, so start early and free up your time because it’s going to get a lot busier for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stay Technical.&lt;/strong&gt; In saying the above, you&apos;ll still need to stay technical. Your new team wants to know you&apos;re technical so they can connect with you. You&apos;ll need to work out a balance between management and tech. Keep in touch with the industry. You&apos;ll also be learning from your team too.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&quot;One Of Us&quot;.&lt;/strong&gt; Work on letting your new team know you are &lt;a href=&quot;https://www.youtube.com/watch?v=GdxuWq1-5qk&quot;&gt;one of them&lt;/a&gt;. Create an environment of openness and trust amongst your team. Work on letting them know you will always be there for them. They will support you when times get rough.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Management is a change of profession.&lt;/strong&gt; Becoming an engineering manager is a different career path and you&apos;ll be starting from scratch. You&apos;re now a junior again. You will do a poor job at the start. Read Charity Major&apos;s &lt;a href=&quot;https://charity.wtf/2017/05/11/the-engineer-manager-pendulum/&quot;&gt;The Engineer/Manager Pendulum&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don’t be afraid to take on the management path.&lt;/strong&gt; You’ll have the full support from your new team, existing managers and business stakeholders. When you connect with them they will become your support model throughout your new challenges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You can always return to being an individual contributor.&lt;/strong&gt; Do not think because you’ve became an engineering manager that you can’t step back to the other path. Remember it’s not a promotion upwards but another path into a different career. When you&apos;re a manager, you pick up skills you don&apos;t experience when you&apos;re an individual contributor: How to have difficult conversations, how to resolve conflicts, how the business works, how to listen, how to communicate, how to provide feedback. These skills are powerful to have and bringing them back to an individual contributor role can be an advantage to your career growth. It was only until I learnt that being a manager is a different career and not a promotion, that I realised it okay to switch and return to building apps. With the added advantage of taking new skills with me - valuable skills I didn&apos;t know existed if I didn&apos;t become an engineering manager.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You have to manage yourself if you want to manage others.&lt;/strong&gt; This would be my biggest lesson. I needed to work on my own self-growth. I needed to be more aware of who I am. How I handled situations in life, how I reacted to emotions and events, finding what drives me in life and in my career. The more I got to know who I am, the more I was able to manage and understand the life&apos;s of others that I needed to manage in the work place.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>