{"id":441,"date":"2024-05-28T12:00:00","date_gmt":"2024-05-28T12:00:00","guid":{"rendered":"https:\/\/fdswebdesign.com\/?p=441"},"modified":"2024-10-15T23:32:28","modified_gmt":"2024-10-15T23:32:28","slug":"the-era-of-platform-primitives-is-finally-here","status":"publish","type":"post","link":"https:\/\/fdswebdesign.com\/index.php\/2024\/05\/28\/the-era-of-platform-primitives-is-finally-here\/","title":{"rendered":"The Era Of Platform Primitives Is Finally Here"},"content":{"rendered":"

The Era Of Platform Primitives Is Finally Here<\/title><\/p>\n<article>\n<header>\n<h1>The Era Of Platform Primitives Is Finally Here<\/h1>\n<address>Atila Fassina<\/address>\n<p> 2024-05-28T12:00:00+00:00<br \/>\n 2024-10-15T23:05:45+00:00<br \/>\n <\/header>\n<p>This article is sponsored by <b>Netlify<\/b><\/p>\n<p>In the past, the web ecosystem moved at a very slow pace. Developers would go years without a new language feature or working around a weird browser quirk. This pushed our technical leaders to come up with creative solutions to circumvent the platform\u2019s shortcomings. We invented bundling, polyfills, and transformation steps to make things work everywhere with less of a hassle.<\/p>\n<p>Slowly, we moved towards some sort of consensus on what we need as an ecosystem. We now have TypeScript and Vite as clear preferences\u2014pushing the needle of what it means to build consistent experiences for the web. Application frameworks have built whole ecosystems on top of them: <a href=\"https:\/\/docs.solidjs.com\/solid-start\">SolidStart<\/a>, <a href=\"https:\/\/nuxtjs.org\/\">Nuxt<\/a>, <a href=\"https:\/\/remix.run\">Remix<\/a>, and <a href=\"https:\/\/analogjs.org\/\">Analog<\/a> are examples of incredible tools built with such primitives. We can say that Vite and TypeScript are tooling primitives that empower the creation of others in diverse ecosystems.<\/p>\n<p>With bundling and transformation needs somewhat defined, it was only natural that framework authors would move their gaze to the next layer they needed to abstract: the server.<\/p>\n<h2 id=\"server-primitives\">Server Primitives<\/h2>\n<p>The UnJS folks have been consistently building agnostic tooling that can be reused in different ecosystems. Thanks to them, we now have frameworks and libraries such as <a href=\"https:\/\/h3.unjs.io\/\">H3<\/a> (a minimal Node.js server framework built with TypeScript), which enables <a href=\"https:\/\/nitro.unjs.io\">Nitro<\/a> (a whole server runtime powered by <a href=\"https:\/\/vite.dev\/\">Vite<\/a>, and <a href=\"https:\/\/h3.unjs.io\/\">H3<\/a>), that in its own turn enabled Vinxi (an application bundler and server runtime that abstracts Nitro and Vite).<\/p>\n<p><a href=\"https:\/\/nitro.unjs.io\">Nitro<\/a> is used already by three major frameworks: <a href=\"https:\/\/nuxtjs.org\/\">Nuxt<\/a>, <a href=\"https:\/\/analogjs.org\/\">Analog<\/a>, and <a href=\"https:\/\/docs.solidjs.com\/solid-start\">SolidStart<\/a>. While Vinxi is also used by SolidStart. This means that any platform which supports one of these, will definitely be able to support the others with <strong>zero additional effort<\/strong>.<\/p>\n<blockquote><p>This is not about taking a bigger slice of the cake. But making the cake bigger for everyone.<\/p><\/blockquote>\n<p>Frameworks, platforms, developers, and users benefit from it. We bet on our ecosystem together instead of working in silos with our monolithic solutions. Empowering our developer-users to gain transferable skills and truly choose the best tool for the job with <strong>less vendor lock-in than ever before<\/strong>.<\/p>\n<h2 id=\"serverless-rejoins-conversation\">Serverless Rejoins Conversation<\/h2>\n<p>Such initiatives have probably been noticed by serverless platforms like Netlify. With <a href=\"https:\/\/www.netlify.com\/platform\/primitives\">Platform Primitives<\/a>, frameworks can leverage agnostic solutions for common necessities such as Incremental Static Regeneration (ISR), Image Optimization, and key\/value (<code>kv<\/code>) storage.<\/p>\n<p>As the name implies, <strong>Netlify Platform Primitives<\/strong> are a group of abstractions and helpers made available at a platform level for either frameworks or developers to leverage when using their applications. This brings additional functionality simultaneously to every framework. This is a big and powerful shift because, up until now, each framework would have to create its own solutions and backport such strategies to compatibility layers within each platform.<\/p>\n<p>Moreover, developers would have to wait for a feature to first land on a framework and subsequently for support to arrive in their platform of choice. Now, as long as they\u2019re using Netlify, those primitives are available directly without any effort and time put in by the framework authors. This empowers every ecosystem in a single measure.<\/p>\n<blockquote><p>Serverless means server infrastructure developers don\u2019t need to handle. It\u2019s not a misnomer, but a format of <strong>Infrastructure As A Service<\/strong>.<\/p><\/blockquote>\n<p>As mentioned before, <a href=\"https:\/\/www.netlify.com\/platform\/primitives\">Netlify Platform Primitives<\/a> are three different features:<\/p>\n<ol>\n<li><strong>Image CDN<\/strong><br \/>\nA <a href=\"https:\/\/www.netlify.com\/blog\/netlify-image-cdn-seamlessly-resize-crop-and-deliver-optimized-media-globally\">content delivery network<\/a> for images. It can handle format transformation and size optimization via URL query strings.<\/li>\n<li><strong>Caching<\/strong><br \/>\nBasic primitives for their server runtime that help manage the caching directives for browser, server, and CDN runtimes smoothly.<\/li>\n<li><strong>Blobs<\/strong><br \/>\nA key\/value (KV) storage option is automatically available to your project through their SDK.<\/li>\n<\/ol>\n<p>Let\u2019s take a quick dive into each of these features and explore how they can increase our productivity with a serverless fullstack experience.<\/p>\n<h3 id=\"image-cdn\">Image CDN<\/h3>\n<p>Every image in a <code>\/public<\/code> can be served through a Netlify function. This means it\u2019s possible to access it through a <code>\/.netlify\/images<\/code> path. So, without adding <a href=\"https:\/\/www.npmjs.com\/package\/sharp\">sharp<\/a> or any image optimization package to your stack, deploying to <a href=\"https:\/\/www.netlify.com\/platform\">Netlify<\/a> allows us to serve our users with a better format without transforming assets at build-time. In a <a href=\"https:\/\/docs.solidjs.com\/solid-start\">SolidStart<\/a>, in a few lines of code, we could have an Image component that transforms other formats to <code>.webp<\/code>.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-ts\">import { type JSX } from \"solid-js\";\n\nconst SITE_URL = \"https:\/\/example.com\";\n\ninterface Props extends JSX.ImgHTMLAttributes<HTMLImageElement> {\n format?: \"webp\" | \"jpeg\" | \"png\" | \"avif\" | \"preserve\";\n quality?: number | \"preserve\";\n}\n\nconst getQuality = (quality: Props[\"quality\"]) => {\n if (quality === \"preserve\") return\"\";\n return `&q=${quality || \"75\"}`;\n};\n\nfunction getFormat(format: Props[\"format\"]) {\n switch (format) {\n case \"preserve\":\n return\" \";\n case \"jpeg\":\n return `&fm=jpeg`;\n case \"png\":\n return `&fm=png`;\n case \"avif\":\n return `&fm=avif`;\n case \"webp\":\n default:\n return `&fm=webp`;\n }\n}\n\nexport function Image(props: Props) {\n return (\n <img\n {...props}\n src={`${SITE_URL}\/.netlify\/images?url=\/${props.src}${getFormat(\n props.format\n )}${getQuality(props.quality)}`}\n \/>\n );\n}\n<\/code><\/pre>\n<\/div>\n<p>Notice the above component is even slightly more complex than bare essentials because we\u2019re enforcing some default optimizations. Our <code>getFormat<\/code> method transforms images to <code>.webp<\/code> by default. It\u2019s a broadly supported format that\u2019s significantly smaller than the most common and without any loss in quality. Our <code>get quality<\/code> function reduces the image quality to 75% by default; as a rule of thumb, there isn\u2019t any perceivable loss in quality for large images while still providing a significant size optimization.<\/p>\n<ul>\n<li><a href=\"https:\/\/primitives-test.netlify.app\">Check our little component at play<\/a>.<\/li>\n<li>Source code: <a href=\"https:\/\/github.com\/atilafassina\/primitives-test\">SolidStart and Netlify Primitives<\/a>.<\/li>\n<\/ul>\n<h3 id=\"caching\">Caching<\/h3>\n<p>By default, Netlify caching is quite extensive for your regular artifacts – unless there\u2019s a new deployment or the cache is flushed manually, resources will last for 365 days. However, because server\/edge functions are dynamic in nature, there\u2019s no default caching to prevent serving stale content to end-users. This means that if you have one of these functions in production, chances are there\u2019s some caching to be leveraged to reduce processing time (and expenses).<\/p>\n<p>By adding a cache-control header, you already have done 80% of the work in optimizing your resources for best serving users. Some commonly used cache control directives:<\/p>\n<pre><code class=\"language-json\">{\n \"cache-control\": \"public, max-age=0, stale-while-revalidate=86400\"\n\n}<\/code><\/pre>\n<ul>\n<li><code>public<\/code>: Store in a shared cache.<\/li>\n<li><code>max-age=0<\/code>: resource is immediately stale.<\/li>\n<li><code>stale-while-revalidate=86400<\/code>: if the cache is stale for less than 1 day, return the cached value and revalidate it in the background.<\/li>\n<\/ul>\n<pre><code class=\"language-json\">{\n \"cache-control\": \"public, max-age=86400, must-revalidate\"\n\n}\n<\/code><\/pre>\n<ul>\n<li><code>public<\/code>: Store in a shared cache.<\/li>\n<li><code>max-age=86400<\/code>: resource is fresh for one day.<\/li>\n<li><code>must-revalidate<\/code>: if a request arrives when the resource is already stale, the cache must be revalidated before a response is sent to the user.<\/li>\n<\/ul>\n<p><strong>Note<\/strong>: <em>For more extensive information about possible compositions of <code>Cache-Control<\/code> directives, check the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Cache-Control\">mdn entry on Cache-Control<\/a>.<\/em><\/p>\n<p>The cache is a type of <strong>key\/value storage<\/strong>. So, once our responses are set with proper cache control, platforms have some heuristics to define what the <code>key<\/code> will be for our resource within the cache storage. The Web Platform has a second very powerful header that can dictate how our cache behaves.<\/p>\n<p>The <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Vary\">Vary response header<\/a> is composed of a list of headers that will affect the validity of the resource (<code>method<\/code> and the endpoint URL are always considered; no need to add them). This header allows platforms to define other headers defined by location, language, and other patterns that will define for how long a response can be considered fresh.<\/p>\n<p>The <strong>Vary<\/strong> response header is a foundational piece of a special header in <a href=\"https:\/\/docs.netlify.com\/platform\/caching\/\">Netlify Caching Primitive<\/a>. The <code>Netlify-Vary<\/code> will take a set of instructions on which parts of the request a key should be based. It is possible to tune a response key not only by the header but also by the <strong>value<\/strong> of the header.<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.netlify.com\/platform\/caching\/#vary-by-query-parameter\">query<\/a>: vary by the value of some or all request query parameters.<\/li>\n<li><a href=\"https:\/\/docs.netlify.com\/platform\/caching\/#vary-by-header\">header<\/a>: vary by the value of one or more request headers.<\/li>\n<li><a href=\"https:\/\/docs.netlify.com\/platform\/caching\/#vary-by-language\">language<\/a>: vary by the languages from the <code>Accept-Language<\/code> header.<\/li>\n<li><a href=\"https:\/\/docs.netlify.com\/platform\/caching\/#vary-by-country\">country<\/a>: vary by the country inferred from a GeoIP lookup on the request IP address.<\/li>\n<li><a href=\"https:\/\/docs.netlify.com\/platform\/caching\/#vary-by-cookie\">cookie<\/a>: vary by the value of one or more request cookie keys.<\/li>\n<\/ul>\n<p>This header offers strong fine-control over how your resources are cached. Allowing for some creative strategies to optimize how your app will perform for specific users.<\/p>\n<h3 id=\"blob-storage\">Blob Storage<\/h3>\n<p>This is a highly-available key\/value store, it\u2019s ideal for frequent reads and infrequent writes. They\u2019re automatically available and provisioned for any Netlify Project.<\/p>\n<p>It\u2019s possible to write on a blob from your runtime or push data for a deployment-specific store. For example, this is how an <a href=\"https:\/\/docs.solidjs.com\/solid-router\/reference\/data-apis\/action\">Action Function<\/a> would register a number of <strong>likes<\/strong> in store with SolidStart.<\/p>\n<pre><code class=\"language-ts\">import { getStore } from \"@netlify\/blobs\";\nimport { action } from \"@solidjs\/router\";\n\nexport const upVote = action(async (formData: FormData) => {\n \"use server\";\n\n const postId = formData.get(\"id\");\n const postVotes = formData.get(\"votes\");\n\n if (typeof postId !== \"string\" || typeof postVotes !== \"string\") return;\n\n const store = getStore(\"posts\");\n const voteSum = Number(postVotes) + 1)\n \n await store.set(postId, String(voteSum);\n\n console.log(\"done\");\n return voteSum\n \n});\n<\/code><\/pre>\n<ul>\n<li>Check <a href=\"https:\/\/docs.netlify.com\/blobs\/overview\/#netlify-blobs-api\"><code>@netlify\/blobs<\/code> API documentation<\/a> for more examples and use-cases.<\/li>\n<\/ul>\n<h2 id=\"final-thoughts\">Final Thoughts<\/h2>\n<p>With high-quality primitives, we can enable library and framework creators to create thin integration layers and adapters. This way, instead of focusing on how any specific platform operates, it will be possible to <strong>focus on the actual user experience<\/strong> and practical use-cases for such features. Monoliths and deeply integrated tooling make sense to build platforms fast with strong vendor lock-in, but that\u2019s not what the community needs. Betting on the web platform is a more sensible and future-friendly way.<\/p>\n<p>Let me know in the comments what your take is about unbiased tooling versus opinionated setups!<\/p>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" class=\"lazyload\" data-src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\"><br \/>\n <span>(il)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>The Era Of Platform Primitives Is Finally Here The Era Of Platform Primitives Is Finally Here Atila Fassina 2024-05-28T12:00:00+00:00 2024-10-15T23:05:45+00:00 This article is sponsored by Netlify In the past, the web ecosystem moved at a very slow pace. Developers would go years without a new language feature or working around a weird browser quirk. This…<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-441","post","type-post","status-publish","format-standard","hentry","category-javascript"],"_links":{"self":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/441","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/comments?post=441"}],"version-history":[{"count":1,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/441\/revisions"}],"predecessor-version":[{"id":442,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/posts\/441\/revisions\/442"}],"wp:attachment":[{"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/media?parent=441"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/categories?post=441"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fdswebdesign.com\/index.php\/wp-json\/wp\/v2\/tags?post=441"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}