<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Web Application Development Archives | DMC, Inc.</title>
	<atom:link href="https://static.dmcinfo.com/blog/category/application-development/web-application-development/feed/index.xml" rel="self" type="application/rss+xml" />
	<link></link>
	<description></description>
	<lastBuildDate>Fri, 04 Sep 2026 21:40:00 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>

<image>
	<url>https://static.dmcinfo.com/wp-content/uploads/2025/04/site-icon-150x150.png</url>
	<title>Web Application Development Archives | DMC, Inc.</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>A Complete Guide to Planning Your IIoT Solution</title>
		<link>https://static.dmcinfo.com/blog/20635/a-complete-guide-to-planning-your-iiot-solution/</link>
		
		<dc:creator><![CDATA[Chase Hollingsead]]></dc:creator>
		<pubDate>Fri, 26 Sep 2025 16:00:00 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Digital Workplace Solutions]]></category>
		<category><![CDATA[Embedded Development & Programming]]></category>
		<category><![CDATA[Ignition]]></category>
		<category><![CDATA[Manufacturing Automation & Intelligence]]></category>
		<category><![CDATA[Test and Measurement Automation]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/?p=20635</guid>

					<description><![CDATA[<p>IoT or Internet of Things is a &#8220;system of interrelated computing devices, mechanical and digital machines, objects, animals, or people provided with unique identifiers and the ability to transfer data over a network without requiring human-to-human or human-to-computer interaction.&#8221; The Internet of Things continues to develop as technology advances, and the need to interact with [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/20635/a-complete-guide-to-planning-your-iiot-solution/">A Complete Guide to Planning Your IIoT Solution</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://static.dmcinfo.com/services/application-development/iot-solutions">IoT or Internet of Things</a> is a &#8220;system of interrelated computing devices, mechanical and digital machines, objects, animals, or people provided with unique identifiers and the ability to transfer data over a network without requiring human-to-human or human-to-computer interaction.&#8221; The Internet of Things continues to develop as technology advances, and the need to interact with devices in a new way continues to develop.</p>



<p class="wp-block-paragraph">Out of IoT, IIoT, or the Industrial Internet of Things, is emerging as a common and necessary term. This guide provides insight into IIoT and into DMC&#8217;s process for completing successful IIoT projects.</p>


<hr />


<h2 id="h-table-of-contents" class="wp-block-heading"><a id="Table of Contents" name="Table of Contents">Table of Contents</a></h2>



<ul class="wp-block-list">
<li style="line-height:30px;font-size:18px"><a href="#IIoT Overview">IIoT Overview</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Our Five-Step Process">Our Five-Step Process</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Step One: Field Device Platform Selection">Step One: Field Device Platform Selection</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Step Two: Determine Communications">Step Two: Determine Communications</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Step Three: Determine Cloud Platform">Step Three: Determine Cloud Platform</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Step Four: Web Application Development">Step Four: Web Application Development</a></li>



<li style="line-height:30px;font-size:18px"><a href="#Step Five: Go Live and Maintenance">Step Five: Go Live and Maintenance</a></li>
</ul>


<hr />


<h2 id="h-iiot-overview" class="wp-block-heading"><a id="IIoT Overview" name="IIoT Overview"></a>IIoT Overview</h2>



<p class="wp-block-paragraph">Much like IoT, IIoT uses sensors and other technology but in an industrial setting to leverage real-time data to monitor and control devices in the field and communicate and display that data in a way that allows for better decision making in industrial processes.</p>


<hr />


<h2 id="h-our-five-step-process" class="wp-block-heading"><a id="Our Five-Step Process" name="Our Five-Step Process"></a>Our Five-Step Process</h2>



<p class="wp-block-paragraph">DMC has completed hundreds of projects incorporating a wide range of solutions. Through experience, our engineers have developed a process for implementing IIoT solutions. We begin by starting at the lowest level and then fill in the gaps, going up the stack.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/IIoT-DMC-Five-Steps.png" alt="DMC IIoT Five Step Process"/></figure>



<h2 id="h-step-one-field-device-platform-selection" class="wp-block-heading"><a id="Step One: Field Device Platform Selection" name="Step One: Field Device Platform Selection"></a>Step One: Field Device Platform Selection</h2>



<p class="wp-block-paragraph">Getting reliable, accurate data from the physical system is the primary challenge for any system design and the most important decision you can make. Once you secure the data, you can do anything you want with it. Answer the following questions to select hardware used in the field:</p>



<ul class="wp-block-list">
<li>What are you trying to measure or control?</li>



<li>What does the device in the field need to be able to do on its own? How complex?</li>



<li>How many devices do you anticipate on deploying?</li>



<li>Are you the end-user and maintainer of this equipment, or are you selling a solution (operated and maintained by others)?</li>



<li>What does new device provisioning look like? (the ideal process)</li>



<li>How will this device be powered?</li>
</ul>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/IIOT-Process-Step-One-Chart.png" alt="Step One"/></figure>



<p class="wp-block-paragraph">Keep in mind, the software platform is determined by hardware selection. For example, when you pick a particular PLC, you have to use the manufacturer&#8217;s&nbsp;software to program it. For an embedded device this will be C++, etc.</p>



<p class="wp-block-paragraph"><strong>Device Hardware: Selecting the Right Parts</strong></p>



<p class="wp-block-paragraph">Let’s say the average PLC costs $1,000, so every time you put one out in the field, that is another $1,000 of hard costs. PLCs are great for&nbsp;some things while NI has developed expert tech for other uses. DMC engineers can leverage experience from hundreds&nbsp;of completed projects to&nbsp;advise on making the best choice for each project.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Device-Hardware-Selecting-the-right-parts-graphic.png" alt=""/></figure>



<p class="wp-block-paragraph"><strong>0-10 Devices</strong> – Small Deployments</p>



<p class="wp-block-paragraph"><strong>10-100 Devices </strong>– Larger Deployment, off the shelf products, but start to have cost-optimized decisions</p>



<p class="wp-block-paragraph"><strong>100-1000+ Devices</strong> &#8211; Discuss an embedded solution because the hardware starts to get expensive</p>



<h2 id="h-step-two-determine-communications" class="wp-block-heading"><a id="Step Two: Determine Communications" name="Step Two: Determine Communications"></a>Step Two: Determine Communications</h2>



<p class="wp-block-paragraph">After determining your device platform, deciding how your networking devices are configured is key. Consistent communication between devices is essential. DMC&#8217;s engineers help scope what needs to be done. Consider the following:</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Step-Two-Determine-Communications-Graphic.png" alt=""/></figure>



<ul class="wp-block-list">
<li>How are you going to communicate with devices?</li>



<li>Where is the internet coming from?
 
 
<ul class="wp-block-list">
<li>Cellular, Wi-Fi, from the plant?</li>
</ul>
</li>



<li>What happens when the internet is not available?
 
 
<ul class="wp-block-list">
<li>Local caching, buffer and retry, operational impacts</li>
</ul>
</li>



<li>What are the protocol security requirements?
 
 
<ul class="wp-block-list">
<li>Encryptions, certificates, secure comm management</li>
</ul>
</li>
</ul>



<h2 id="h-step-three-determine-cloud-platform" class="wp-block-heading"><a id="Step Three: Determine Cloud Platform" name="Step Three: Determine Cloud Platform"></a>Step Three: Determine Cloud Platform</h2>



<p class="wp-block-paragraph">There are a lot of cloud platforms to choose from when you reach this phase of the process.&nbsp;Ask yourself, what out of the box services (provided by these hosting entities) will your application need/or take advantage of? Some cloud platforms include&nbsp;Azure,&nbsp;AWS, and&nbsp;Google.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cloud-Platforms.png" alt=""/></figure>



<p class="wp-block-paragraph">This phase is when we need to assess how to save on custom development, and where it&#8217;s possible to use solid foundational pieces already developed for these types of applications. Ask yourself:</p>



<ul class="wp-block-list">
<li>Do you need a website?</li>



<li>Do you need database(s)?</li>



<li>Do you need user management?</li>



<li>Do you need integrations to other cloud services?</li>



<li>Do you need SMS, e-Mail, or other mass notification capabilities?</li>



<li>Do you need an AI engine or advanced analytics support?</li>



<li>Do you need a flexible reporting framework (points to things like PowerBI)?</li>



<li>What type of data store is needed?
 
 
<ul class="wp-block-list">
<li>How much data?</li>



<li>How often will it be sampled?</li>



<li>How will the data be used?</li>
</ul>
</li>



<li>Where and how will security be enforced for cloud resources?</li>



<li>How many monthly active users do you anticipate on this cloud application?</li>



<li>What in house cloud/web development resources do you have?
 
 
<ul class="wp-block-list">
<li>What are they comfortable with and willing to maintain?</li>
</ul>
</li>
</ul>



<h2 id="h-step-four-web-application-development" class="wp-block-heading"><a id="Step Four: Web Application Development" name="Step Four: Web Application Development"></a>Step Four: Web Application Development</h2>



<p class="wp-block-paragraph">DMC’s full-stack development team builds custom web applications with&nbsp;<a href="https://static.dmcinfo.com/services/application-development/#interfaces-and-backends">intuitive interfaces designed for usability and stable back ends designed for scalability.</a>&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/C2Sense-Dashboard-Updated-2-1.png" alt=""/></figure>



<p class="wp-block-paragraph">Consider the following during this step:</p>



<ul class="wp-block-list">
<li>Define the UI/UX experience</li>



<li>How are you going to onboard new users?</li>



<li>How are you going to onboard new devices?</li>



<li>How are you going to manage devices?</li>



<li>How are users going to view data?</li>



<li>What access restrictions should apply? (user levels)</li>



<li>What types of notification and alerts are required?</li>



<li>When should devices be alerted to changes?</li>



<li>What visualizations for data or information are required?</li>



<li>What type of reporting is required? How are users notified of reports?</li>



<li>Is a native Mobile App also required?</li>



<li>Is a generic API (accessible by third parties) required?</li>



<li>Define support plan for end-users of the application</li>
</ul>



<h2 id="h-step-five-go-live-and-maintenance" class="wp-block-heading"><a id="Step Five: Go Live and Maintenance" name="Step Five: Go Live and Maintenance"></a>Step Five: Go Live and Maintenance</h2>



<ul class="wp-block-list">
<li>Are you using continuous Integration tools in Development, Staging, and Production environments?</li>



<li>Do you have planned downtime for production-level updates?</li>



<li>Are database migrations required? Data integrity checks?</li>



<li>Deploy and active service health monitors?</li>



<li>Are support and service avenues (emails/phone) active and being monitored?</li>
</ul>



<div class="wp-block-group has-global-padding is-layout-constrained wp-container-core-group-is-layout-69f7dce8 wp-block-group-is-layout-constrained" style="padding-top:var(--wp--preset--spacing--60);padding-right:0;padding-bottom:var(--wp--preset--spacing--60);padding-left:0">
<div class="wp-block-group alignwide is-layout-flow wp-block-group-is-layout-flow">
<h2 id="h-industry-credentials" class="wp-block-heading">Industry Credentials</h2>



<p class="wp-block-paragraph">DMC holds several key industry credentials with leading technology providers.</p>



<div class="wp-block-cb-carousel-v2 cb-carousel-block" data-cb-slides-per-view="1" data-cb-slides-per-group="1" data-cb-space-between="20" data-cb-speed="300" data-cb-navigation="true" data-cb-pagination="true" data-cb-loop="true" data-cb-autoplay="true" data-cb-autoplay-speed="1500" data-cb-breakpoints="{&quot;768&quot;:{&quot;slidesPerView&quot;:5,&quot;slidesPerGroup&quot;:1}}"><div class="swiper"><div class="cb-wrapper swiper-wrapper">
<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-6d407f44 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="500" height="308" src="https://static.dmcinfo.com/wp-content/uploads/2025/09/Silver-Alliance-Partner-Membership-Logo.png" alt="National Instruments membership logo" class="wp-image-38744" style="object-fit:cover;width:161px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/09/Silver-Alliance-Partner-Membership-Logo.png 500w, https://static.dmcinfo.com/wp-content/uploads/2025/09/Silver-Alliance-Partner-Membership-Logo-300x185.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="700" height="340" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/siemens-solution-partner-logo-1.jpg" alt="siemens solution partner logo" class="wp-image-20625" style="width:164px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/siemens-solution-partner-logo-1.jpg 700w, https://static.dmcinfo.com/wp-content/uploads/2025/05/siemens-solution-partner-logo-1-300x146.jpg 300w" sizes="(max-width: 700px) 100vw, 700px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="1452" height="335" src="https://static.dmcinfo.com/wp-content/uploads/2025/06/Microsoft-Solutions-Partner-logo.png" alt="Microsoft Solutions Partner" class="wp-image-31568" style="width:155px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/06/Microsoft-Solutions-Partner-logo.png 1452w, https://static.dmcinfo.com/wp-content/uploads/2025/06/Microsoft-Solutions-Partner-logo-300x69.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/06/Microsoft-Solutions-Partner-logo-1024x236.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/06/Microsoft-Solutions-Partner-logo-768x177.png 768w" sizes="(max-width: 1452px) 100vw, 1452px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group wp-container-content-69bc4bdf is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="350" height="350" src="https://static.dmcinfo.com/wp-content/uploads/2025/04/image-120.png" alt="Ignition Premier Integrator" class="wp-image-2545" style="object-fit:cover;width:108px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/04/image-120.png 350w, https://static.dmcinfo.com/wp-content/uploads/2025/04/image-120-300x300.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/04/image-120-150x150.png 150w" sizes="(max-width: 350px) 100vw, 350px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="4000" height="1484" src="https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB.png" alt="Aveva Partner badge" class="wp-image-33216" style="object-fit:cover;width:144px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB.png 4000w, https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB-300x111.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB-1024x380.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB-768x285.png 768w, https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB-1536x570.png 1536w, https://static.dmcinfo.com/wp-content/uploads/2025/06/AVEVA-Partner-Badge_System-Integrator_Certified_RGB-2048x760.png 2048w" sizes="(max-width: 4000px) 100vw, 4000px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="700" height="168" src="https://static.dmcinfo.com/wp-content/uploads/2025/03/kepware-logo-resized.png" alt="Kepware logo" class="wp-image-136" style="width:152px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/03/kepware-logo-resized.png 700w, https://static.dmcinfo.com/wp-content/uploads/2025/03/kepware-logo-resized-300x72.png 300w" sizes="(max-width: 700px) 100vw, 700px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="750" height="233" src="https://static.dmcinfo.com/wp-content/uploads/2025/06/Rockwell-Gold-System-Integrator-2.png" alt="Rockwell Gold System Integrator" class="wp-image-32507" style="width:167px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/06/Rockwell-Gold-System-Integrator-2.png 750w, https://static.dmcinfo.com/wp-content/uploads/2025/06/Rockwell-Gold-System-Integrator-2-300x93.png 300w" sizes="(max-width: 750px) 100vw, 750px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="350" height="134" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Xively-Partner-Logo_1.png" alt="Xively Partner Logo 1" class="wp-image-20631" style="width:167px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/Xively-Partner-Logo_1.png 350w, https://static.dmcinfo.com/wp-content/uploads/2025/05/Xively-Partner-Logo_1-300x115.png 300w" sizes="(max-width: 350px) 100vw, 350px" /></figure>
</div>
</div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-group has-custom-light-gray-neutral-2-background-color has-background has-global-padding is-layout-constrained wp-container-core-group-is-layout-aa06d7d7 wp-block-group-is-layout-constrained" style="border-radius:20px;min-height:175px;padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)">
<div class="wp-block-group is-vertical is-content-justification-center is-layout-flex wp-container-core-group-is-layout-ec272ca4 wp-block-group-is-layout-flex" style="min-height:130px">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="2560" height="652" src="https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo.png" alt="MQTT-logo" class="wp-image-38746" style="width:167px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo.png 2560w, https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo-300x76.png 300w, https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo-1024x261.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo-768x196.png 768w, https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo-1536x391.png 1536w, https://static.dmcinfo.com/wp-content/uploads/2019/10/MQTT-logo-2048x522.png 2048w" sizes="(max-width: 2560px) 100vw, 2560px" /></figure>
</div>
</div>
</div>
</div></div><div class="cb-pagination swiper-pagination"></div><div class="cb-button-prev swiper-button-prev"></div><div class="cb-button-next swiper-button-next"></div></div>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-outline is-style-outline--1"><a class="wp-block-button__link wp-element-button" href="https://static.dmcinfo.com/about/partners/">View all partners</a></div>
</div>
</div>
</div>



<h2 id="h-our-work" class="wp-block-heading">Our Work</h2>



<div class="wp-block-group alignwide is-layout-flow wp-block-group-is-layout-flow">
<div class="wp-block-cb-carousel-v2 cb-carousel-block" data-cb-slides-per-view="1" data-cb-slides-per-group="1" data-cb-space-between="15" data-cb-speed="300" data-cb-navigation="true" data-cb-pagination="true" data-cb-loop="true" data-cb-autoplay="true" data-cb-autoplay-speed="3000" data-cb-breakpoints="{&quot;768&quot;:{&quot;slidesPerView&quot;:3,&quot;slidesPerGroup&quot;:1}}"><div class="swiper"><div class="cb-wrapper swiper-wrapper">
<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-14893 our_work type-our_work status-publish has-post-thumbnail hentry work_category-application-development work_category-battery-pack-test-systems work_category-consumer-goods work_category-internet-of-things-iot work_category-test-measurement-automation work_category-user-interface-design work_category-web-application-development">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/aws-cloud-hosted-battery-management-system/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-96.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="AWS Cloud Hosted Battery Management System" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-96.png 1400w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-96-300x107.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-96-1024x366.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-96-768x274.png 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-1 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/aws-cloud-hosted-battery-management-system/" target="_self" >AWS Cloud Hosted Battery Management System</a></h4>
</li></ul></div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-14800 our_work type-our_work status-publish has-post-thumbnail hentry work_category-application-development work_category-azure work_category-chemical work_category-digital-workplace-solutions work_category-energy-and-utilities work_category-internet-of-things-iot work_category-manufacturing-automation-and-intelligence work_category-plc-programming work_category-siemens-plc work_category-user-interface-design work_category-web-application-development">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/iiot-interface-for-agricultural-irrigation-injection-system/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-86.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="IIoT Interface for Agricultural Irrigation Injection System" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-86.png 1400w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-86-300x107.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-86-1024x366.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-86-768x274.png 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-2 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/iiot-interface-for-agricultural-irrigation-injection-system/" target="_self" >IIoT Interface for Agricultural Irrigation Injection System</a></h4>
</li></ul></div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-14433 our_work type-our_work status-publish has-post-thumbnail hentry work_category-application-development work_category-automotive work_category-azure work_category-digital-workplace-solutions work_category-energy-and-utilities work_category-infrastructure work_category-internet-of-things-iot work_category-manufacturing-automation-and-intelligence work_category-oil-and-gas-engineering work_category-plc-programming work_category-web-application-development">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/vehicle-fleet-tracking-and-monitoring-system/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-41.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="Vehicle Fleet Tracking and Monitoring System" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-41.png 1400w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-41-300x107.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-41-1024x366.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-41-768x274.png 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-3 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/vehicle-fleet-tracking-and-monitoring-system/" target="_self" >Vehicle Fleet Tracking and Monitoring System</a></h4>
</li></ul></div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-43049 our_work type-our_work status-publish has-post-thumbnail hentry work_category-magnemotion work_category-manufacturing-automation-and-intelligence work_category-rockwell-factorytalk work_category-semiconductor">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/semiconductor-automated-material-handling-system-amhs/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2026/04/semiconductor-AMHS-modernization-for-manufacturing.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="Semiconductor AMHS for Scalable, High-Throughput Manufacturing Facility" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2026/04/semiconductor-AMHS-modernization-for-manufacturing.jpg 1400w, https://static.dmcinfo.com/wp-content/uploads/2026/04/semiconductor-AMHS-modernization-for-manufacturing-300x107.jpg 300w, https://static.dmcinfo.com/wp-content/uploads/2026/04/semiconductor-AMHS-modernization-for-manufacturing-1024x366.jpg 1024w, https://static.dmcinfo.com/wp-content/uploads/2026/04/semiconductor-AMHS-modernization-for-manufacturing-768x274.jpg 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-4 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/semiconductor-automated-material-handling-system-amhs/" target="_self" >Semiconductor AMHS for Scalable, High-Throughput Manufacturing Facility</a></h4>
</li></ul></div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-14788 our_work type-our_work status-publish has-post-thumbnail hentry work_category-application-development work_category-automotive work_category-internet-of-things-iot work_category-manufacturing-automation-and-intelligence work_category-plc-programming work_category-siemens-plc work_category-user-interface-design work_category-web-application-development">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/data-collection-through-iot-using-siemens-mqtt-aws-and-react/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-84.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="Data Collection Through IoT Using Siemens, MQTT, AWS, and REACT" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-84.png 1400w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-84-300x107.png 300w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-84-1024x366.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2025/05/unnamed-file-84-768x274.png 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-5 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/data-collection-through-iot-using-siemens-mqtt-aws-and-react/" target="_self" >Data Collection Through IoT Using Siemens, MQTT, AWS, and REACT</a></h4>
</li></ul></div>
</div>



<div class="wp-block-cb-slide-v2 cb-slide swiper-slide">
<div class="wp-block-query is-layout-flow wp-block-query-is-layout-flow"><ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow"><li class="wp-block-post post-14525 our_work type-our_work status-publish has-post-thumbnail hentry work_category-application-development work_category-embedded-development-programming work_category-energy-and-utilities work_category-internet-of-things-iot">
<figure class="wp-block-post-featured-image"><a href="https://static.dmcinfo.com/our-work/solar-powered-embedded-iot-solution-for-measuring-liquid-during-transport/" target="_self" ><img decoding="async" width="1400" height="500" src="https://static.dmcinfo.com/wp-content/uploads/2017/01/solar-powered-IoT-solution.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="Solar-powered Embedded IoT Solution for Measuring Liquid During Transport" style="border-radius:20px;object-fit:cover;" srcset="https://static.dmcinfo.com/wp-content/uploads/2017/01/solar-powered-IoT-solution.png 1400w, https://static.dmcinfo.com/wp-content/uploads/2017/01/solar-powered-IoT-solution-300x107.png 300w, https://static.dmcinfo.com/wp-content/uploads/2017/01/solar-powered-IoT-solution-1024x366.png 1024w, https://static.dmcinfo.com/wp-content/uploads/2017/01/solar-powered-IoT-solution-768x274.png 768w" sizes="(max-width: 1400px) 100vw, 1400px" /></a></figure>

<h4 class="has-link-color wp-elements-6 wp-block-post-title"><a href="https://static.dmcinfo.com/our-work/solar-powered-embedded-iot-solution-for-measuring-liquid-during-transport/" target="_self" >Solar-powered Embedded IoT Solution for Measuring Liquid During Transport</a></h4>
</li></ul></div>
</div>
</div></div><div class="cb-pagination swiper-pagination"></div><div class="cb-button-prev swiper-button-prev"></div><div class="cb-button-next swiper-button-next"></div></div>
</div>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Transform Data into Actionable Insights</strong> for Your IIoT Initiatives.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Plan and implement a successful <a href="https://static.dmcinfo.com/services/manufacturing-automation-and-intelligence/industrial-iot/" id="566">IIoT strategy</a> with support from DMC&#8217;s engineering experts. Connect with us and see how DMC can take your <a href="https://static.dmcinfo.com/services/manufacturing-automation-and-intelligence/" id="420">Automation</a> project to the next level. </p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/20635/a-complete-guide-to-planning-your-iiot-solution/">A Complete Guide to Planning Your IIoT Solution</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Electrifying Desktop Application Development with Electron</title>
		<link>https://static.dmcinfo.com/blog/15849/electrifying-desktop-application-development-with-electron/</link>
		
		<dc:creator><![CDATA[DMC]]></dc:creator>
		<pubDate>Mon, 18 Nov 2024 10:58:38 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[PC Application Development]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<category><![CDATA[Electron]]></category>
		<category><![CDATA[JavaScript]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/15849/electrifying-desktop-application-development-with-electron/</guid>

					<description><![CDATA[<p>You&#8217;ve already used an Electron application.&#160;So, what is Electron? You’ve already used an Electron application; you just might not have known it. Electron applications provide the basis for some of the apps you use daily—Microsoft Teams, Slack, and Visual Studio Code. Electron is a modern, open-source framework for building desktop applications. Since it runs on [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/15849/electrifying-desktop-application-development-with-electron/">Electrifying Desktop Application Development with Electron</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 id="h-you-ve-already-used-an-electron-application-nbsp-so-what-is-electron" class="wp-block-heading">You&#8217;ve already used an Electron application.&nbsp;So, what is Electron?</h2>



<p class="wp-block-paragraph">You’ve already used an Electron application; you just might not have known it. Electron applications provide the basis for some of the apps you use daily—Microsoft Teams, Slack, and Visual Studio Code.</p>



<p class="wp-block-paragraph"><a href="https://www.electronjs.org/" target="_blank" rel="noreferrer noopener">Electron</a> is a modern, open-source framework for building desktop applications. Since it runs on the Node.js runtime, Electron makes it possible to harness JavaScript—typically a web technology—for the development of desktop applications. That’s a powerful way to take our expertise in web technology and apply it to desktop development.</p>



<p class="wp-block-paragraph">Let’s talk about why DMC loves building with Electron!</p>



<h2 id="h-why-dmc-develops-with-electron" class="wp-block-heading">Why DMC develops with Electron</h2>



<p class="wp-block-paragraph"><strong>Electron is cross-platform.</strong> Have a use case that calls for compatibility with Windows, macOS, and Linux? Your Electron application will run on all three, so you need just one code base. You’ll benefit from a smaller, tighter engineering team focused on a single application for minimized time to market and maximized market reach across multiple platforms. A singular code base means that bug fixes, updates, and new features need only be implemented once.</p>



<p class="wp-block-paragraph"><strong>Built on the Node.js runtime.</strong> Equally as important is the runtime itself. Electron runs on Node.js, a JavaScript runtime environment, taking advantage of JavaScript’s position as the<a href="https://survey.stackoverflow.co/2023/#technology-most-popular-technologies"> world’s eminent web technology</a>. Not only does this align with DMC’s sweeping base of expertise in developing JavaScript-based applications, but it also means that you’ll be able to involve your developers in the technical process during an application’s development and support, including post-handoff of the application. That common technical understanding means all parties are speaking the same language and are geared up for success for the lifetime of the application.</p>



<p class="wp-block-paragraph"><strong>Prototyping and proof-of-concepting. </strong>An often-understated utility of Electron development is its handiness for prototyping. Since Electron applications can be spun up quickly — and because you&#8217;ll have access to the vast community of established libraries and frameworks built for JavaScript — an Electron application is ideally suited to building out a viable proof of concept perfect for getting projects off to a promising start.</p>



<p class="wp-block-paragraph"><strong>Trying out new ideas with Electron Fiddle.</strong> Sometimes, all you need is a playground to test out that idea that’s been bouncing around in your head. Electron Fiddle is Electron’s take on <a href="https://jsfiddle.net/">JSFiddle</a>. It’s a lightweight desktop studio for quickly running a simple Electron project. Maybe you want to experiment with a new feature for your application. Or maybe you want to download someone else’s demo and click around on your own. Fiddle allows you&nbsp;to run an Electron application without the overhead of initializing a completely new project. I love this capability and use it both for exploring new features and testing out small tweaks to colleagues’ code. You can read up on <a href="https://www.electronjs.org/fiddle">Electron Fiddle</a> here.</p>



<p class="wp-block-paragraph"><strong>Packaging and Distributing with Electron Forge. </strong>Electron Forge is the all-in-one tool for initiating, configuring, pipelining, and distributing an Electron application. At the start of your project, you’ll use the Electron Forge CLI to spin up a template application. Later, when it’s time to distribute, Electron Forge facilitates three core steps in the distribution sequence:</p>



<ol class="wp-block-list">
<li>The <em>packaging</em> of the application to a bundled executable</li>



<li>The <em>making</em> of the bundled executable into a distributable (such as a .zip or an .exe)</li>



<li>The <em>publishing</em> of the distributable so that your application is available for users to download</li>
</ol>



<h2 id="h-users-love-it" class="wp-block-heading">Users love it</h2>



<p class="wp-block-paragraph">Developing for the web means recognizing the dominant design for the everyday user’s experience. Developers and non-developers alike have come to expect a certain standard: a modern browser serving webpages on a modern JavaScript framework—think React with Material UI or Angular with Angular UI. Why shouldn’t this expectation carry over to the desktop application experience? For consistency and seamlessness, it makes sense to craft desktop applications that look and feel like their web counterparts.</p>



<p class="wp-block-paragraph">An Electron application fluently brings the desktop application must-haves—offline capabilities, local system access, process launching, hardware discovery—and merges them with the familiar user interface elements of the web.</p>



<p class="wp-block-paragraph">When it comes to picking JavaScript frameworks and libraries, Electron lets you choose your own tools. You can keep things simple and lean on basic HTML and CSS. Or you can scaffold up a full-fledged React application via Next.js.</p>



<h2 id="h-case-study-a-desktop-application-for-high-throughput-data-analysis-and-visualization" class="wp-block-heading">Case Study: A desktop application for high-throughput data analysis and visualization</h2>



<p class="wp-block-paragraph"><b>The problem: </b>Our client, a global aerospace and defense technology company, sought to modernize their in-house data analysis and visualization workflow. Their existing workflow relied on several de-coupled applications for processing and exhibiting their data, and there was no official means of warehousing the data. Their team needed a successor platform to provide stronger scalability and faster workflow throughput while still retaining the data reporting functionalities of the legacy system. In modernizing the legacy platform, they laid out a set of key requirements:</p>



<ul class="wp-block-list">
<li>The solution must provide for the storage of captured data.</li>



<li>The solution must ingest large datasets and visualize them clearly, and it must do so performantly.
 
 
<ul class="wp-block-list">
<li>Presentation of data must support advanced visualization controls such as time-shifting, zooming, and time-trending.</li>



<li>Plotting must be capable of processing up to 1 million data points.</li>
</ul>
</li>



<li>The solution must support the validation of data.</li>



<li>The solution’s data processing must be offline-capable. If data is available locally, no network connection shall be required for the core functionality of the application.</li>
</ul>



<p class="wp-block-paragraph"><strong>The solution:</strong> DMC identified a path forward to replace the legacy workflow platform with a single cohesive solution. The bedrock of the solution is a data analysis desktop application built on Electron. Here’s why we landed on Electron for building out this application:</p>



<ul class="wp-block-list">
<li>Electron gives our development team access to a range of powerful JavaScript packages for plotting, UI, and navigation. These are the packages that supply the familiar, expected experience of the web, and Electron brings them to the desktop environment.</li>



<li>Electron’s access to the native OS empowers us to launch a local Python service upon Electron startup. This allows us to easily take advantage of Python’s proven libraries for data crunching, validation, and report generation.</li>
</ul>



<p class="wp-block-paragraph">To round out the solution, we then spun up cloud-based data storage hosted on Amazon’s S3 service. As a whole, DMC delivered an application to meet the&nbsp;client&#8217;s need for a comprehensive data intelligence and reporting platform.</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Bring Modern Web Experiences to the Desktop</strong></h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in Electron, JavaScript, and cross-platform architectures for building scalable <a href="https://static.dmcinfo.com/services/application-development/desktop-application-development/" data-type="page" data-id="680">desktop applications</a> from a unified codebase.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/15849/electrifying-desktop-application-development-with-electron/">Electrifying Desktop Application Development with Electron</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Image Processing Basics and How to Code Your Own Algorithms</title>
		<link>https://static.dmcinfo.com/blog/15899/image-processing-basics-and-how-to-code-your-own-algorithms/</link>
		
		<dc:creator><![CDATA[DMC]]></dc:creator>
		<pubDate>Fri, 25 Oct 2024 16:32:15 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[Manufacturing Automation & Intelligence]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/15899/image-processing-basics-and-how-to-code-your-own-algorithms/</guid>

					<description><![CDATA[<p>Having a passion for image processing informs my work on machine vision projects and allows me to integrate my personal passions and professional expertise. Image processing intrinsically involves a lot of math and heavy&#160;computation. It is crucial to understand how and where you can optimize the code. This blog will introduce you to the basics [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/15899/image-processing-basics-and-how-to-code-your-own-algorithms/">Image Processing Basics and How to Code Your Own Algorithms</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Having a passion for image processing informs my work on <a href="https://static.dmcinfo.com/services/manufacturing-automation-and-intelligence/vision-inspection">machine vision</a> projects and allows me to integrate my personal passions and professional expertise. Image processing intrinsically involves a lot of math and heavy&nbsp;computation. It is crucial to understand how and where you can optimize the code. This blog will introduce you to the basics of image processing.&nbsp;</p>



<p class="wp-block-paragraph">Fair warning that I will introduce several mathematical concepts that you should understand to truly grasp image processing concepts, but they may not be necessary for algorithm implementation.</p>



<h2 id="h-terminology-nbsp" class="wp-block-heading">Terminology&nbsp;</h2>



<ul class="wp-block-list">
<li><strong>Pixel </strong>(aka picture element) &#8211; the smallest addressable element in a raster image.&nbsp;</li>



<li><strong>Kernel </strong>(aka convolution matrix, mask) &#8211; a matrix that allows you to manipulate images through convolution or cross-correlation.&nbsp;</li>



<li><strong>Convolution</strong> &#8211; an operation on two functions that produces an output function as described in the math section. &nbsp;</li>



<li><strong>Cross-correlation</strong> – similar to convolution, but differs in the domain of operation as well as the preprocessing of input functions.&nbsp;</li>



<li><strong>Pixel weight</strong> – the level of contribution a given pixel has on the final output.&nbsp;</li>



<li><strong>Image noise</strong> – random variation of information within a given image.&nbsp;&nbsp;</li>
</ul>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="669" height="892" src="https://static.dmcinfo.com/wp-content/uploads/2024/10/image-processing-no-noise.jpg.webp" alt="image processing no noise" class="wp-image-37312" style="width:407px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2024/10/image-processing-no-noise.jpg.webp 669w, https://static.dmcinfo.com/wp-content/uploads/2024/10/image-processing-no-noise.jpg-225x300.webp 225w" sizes="(max-width: 669px) 100vw, 669px" /></figure>



<p class="wp-block-paragraph"><br> <em>Regular image (left) vs image containing excessive noise (right)</em></p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="669" height="892" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-noise.png" alt="Coffee cup with noise" class="wp-image-15885" style="width:482px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-noise.png 669w, https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-noise-225x300.png 225w" sizes="(max-width: 669px) 100vw, 669px" /></figure>



<p class="wp-block-paragraph"> <em>Regular image (left) vs image containing excessive noise (right)</em></p>



<h2 id="h-convolution-vs-cross-correlation" class="wp-block-heading">Convolution vs Cross-Correlation</h2>



<p class="wp-block-paragraph">When talking about image processing, many people mistakenly refer to convolution in place of cross-correlation. Here is the mathematical representation of convolution, where you must invert the shifted multiplier.&nbsp;</p>



<p class="wp-block-paragraph"><img decoding="async" alt="Convolution" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Convolution.png"><br>
<em>Convolution</em></p>



<p class="wp-block-paragraph">On the contrary, in cross-correlation, notice that we leave the shifted multiplier as is and instead take the complex conjugate of the main function, operation that would be ignored if working strictly in the real domain.&nbsp;</p>



<p class="wp-block-paragraph"><img decoding="async" alt="Cross-correlation" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation.png"><br>
<em>Cross-correlation</em></p>



<p class="wp-block-paragraph"><img decoding="async" alt="Cross-correlation in the real domain" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-real-domain.png"><br>
Cross-correlation in the real domain</p>



<p class="wp-block-paragraph">It is quite important to note that convolution is commutative, where cross-correlation is not. That said, in reality, most if not all algorithms, technically, use cross-correlation instead of convolution (yes, including convolutional neural networks). You could potentially use either method, however, the output orientation will be an inverse of one another. This is because the preprocessing of each input function differs in the orientation.&nbsp;</p>



<p class="wp-block-paragraph">The following is the convolution and cross-correlation visual demonstration. Keep in mind that the integral is going to be the same for two shifted multipliers that are opposite in the orientation, so this demonstration works for both operations depending on how you pre-process the shifted multiplier.</p>



<p class="wp-block-paragraph"><img decoding="async" alt="convolution and cross-correlation visual demonstration" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-1_1.png">&nbsp;<img decoding="async" alt="convolution and cross-correlation visual demonstration" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-2.png"><br>
<img decoding="async" alt="convolution and cross-correlation visual demonstration" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-3.png">&nbsp;<img decoding="async" alt="convolution and cross-correlation visual demonstration" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-4.png"><br>
<img decoding="async" alt="convolution and cross-correlation visual demonstration" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Cross-correlation-5.png"><br>
<em>Blue unit function is the main function.&nbsp;Orange reverse identity function is the shifted multiplier.&nbsp;Blue area is the result of operation at a given moment.&nbsp;Green line is the output of cross-correlation (convolution).&nbsp;</em></p>



<h2 id="h-formats-and-grayscale-nbsp" class="wp-block-heading">Formats and Grayscale&nbsp;</h2>



<p class="wp-block-paragraph">There are multiple different formats that you can use to save your images.&nbsp;</p>



<ul class="wp-block-list">
<li><strong>Raster images</strong> – images that have a predefined pixel proportion/density. You cannot resize these images without compromising the quality of the output. The common formats in this category are JPEG, GIF, PNG.&nbsp;</li>



<li><strong>Vector images</strong> – these images are constructed using proportional formulas, which makes them much more suitable for resizing. PDF, AI, SVG and EPS are such formats.&nbsp;</li>
</ul>



<p class="wp-block-paragraph">I will be focusing on raster images since they are direct representations of pixels and their locations.&nbsp;Additionally, to show image processing concepts, I will be utilizing grayscale images to avoid the complexity of dealing with multiple color channels.&nbsp;</p>



<p class="wp-block-paragraph">There are different ways to convert a colored image into grayscale, however, my choice is <strong>ITU-R Recommendation BT.709</strong>.</p>



<p class="wp-block-paragraph"><img decoding="async" alt="greyscale conversion code" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/greyscale-conversion.png"><br>
<em>R, G, and B – red, green, and blue channel intensities, respectively</em></p>



<p class="wp-block-paragraph"><img decoding="async" alt="Coffe cup in color" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-procesing-color.jpg">&nbsp;<img decoding="async" alt="Coffee cup in greyscale" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-greyscale.jpg"><br>
<em>Color image vs grayscale image</em></p>



<h2 id="h-common-filters-nbsp" class="wp-block-heading">Common Filters&nbsp;</h2>



<h3 id="h-blur-nbsp" class="wp-block-heading">Blur&nbsp;</h3>



<p class="wp-block-paragraph">There are different kinds of blurring techniques that are commonly used in image processing. To name a few: average, median, gaussian blurring. The main purpose of the blurring techniques is to reduce the noise of the image, or in other words to smooth the image out.&nbsp;</p>



<p class="wp-block-paragraph"><img decoding="async" alt="Coffee cup with types of blur" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-blur.png"><em>Left to right: original image, gaussian blur, average blur, median blur</em></p>



<h3 id="h-edge-detection-nbsp" class="wp-block-heading">Edge detection&nbsp;</h3>



<p class="wp-block-paragraph">Edge detection is another common algorithm that allows you to detect visible edges on a given image. This filter is commonly used in feature extractions of convolutional neural networks. You can detect edges in either x or y direction or choose both in parallel. You can also change the orientation of the edge detection from top to bottom and vice versa or from left to right and vice versa.</p>



<p class="wp-block-paragraph"><img decoding="async" alt="Coffee cup with edge detection" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/image-processing-edge-detection.png"><br>
<em>Original image vs edge detection outcome</em></p>



<h2 id="h-sandbox-setup-nbsp" class="wp-block-heading">Sandbox Setup&nbsp;</h2>



<p class="wp-block-paragraph">You can use the following code to&nbsp;build&nbsp;image processing algorithms. Python is my language of choice. It has extensive community support and a plethora of extremely helpful libraries that allow you to develop image processing concepts efficiently. However, you can utilize any language that you choose.&nbsp;</p>



<p class="wp-block-paragraph">We will require the following libraries and classes to facilitate our algorithms:&nbsp;</p>


<pre class="”brush:python”">
import numpy as np 
from PIL import Image 
</pre>


<p class="wp-block-paragraph">All the code will be defined within the parent ImageProcessor class that will outsource algorithms from helper classes. To run the code for testing we will set up a main function that will instantiate the ImageProcessor class that will give you access to desired functionality. For best practice, do instantiate the class in its own file.&nbsp;</p>


<pre class="”brush:python”">
from image_processor import ImageProcessor 

 

if __name__ == &quot;__main__&quot;: 

    image_processor = ImageProcessor(&quot;images/dmc-mug.png&quot;) 
</pre>


<p class="wp-block-paragraph">Now, let’s see how you can set up the ImageProcessor class. The data to store in the ImageProcessor is the image data that will be required to produce desired results. Such data for now is the filename that will allow us to open the file, image in its original color code and the grayscale mode of the same image.&nbsp;</p>


<pre class="”brush:python”">
class ImageProcessor: 

    def __init__(self, filename: str) -&gt; None: 

        # Image data. 

        self.filename: str = filename 

        self.image: np.ndarray = self.open_image(filename) 

        self.gray_image: np.ndarray = self.to_grayscale(self.image) 
</pre>


<p class="wp-block-paragraph">To open, show, and save the images, you can use an external library called <a href="https://pypi.org/project/pillow/" target="_blank">pillow</a>. Do keep in mind that to save the images in the PNG format from a <a href="https://numpy.org/" target="_blank">numpy</a> array, you must cast the type, otherwise, you will get an empty image.</p>


<pre class="”brush:python”">
  def open_image(self, filename): 

        with Image.open(filename) as raw_image: 

            return np.array(raw_image.convert(&quot;RGB&quot;)) 

         

    def save_image(self, filename, data): 

        image = Image.fromarray(np.uint8(data)) 

        image.save(filename, &quot;PNG&quot;) 

 

    def show_image(self, image): 

        img = Image.fromarray(image.astype(np.uint8)) 

        img.show() 
</pre>


<p class="wp-block-paragraph">Converting an image to grayscale can also be easily done like so:</p>


<pre class="”brush:python”">
 def to_grayscale(self, image): 

        gray_image = np.zeros((image.shape[0], image.shape[1])) 

 

        # ITU-R Recommendation BT.709: Y = 0.2126R + 0.7152G + 0.0722B - selected 

        # ITU-R Recommendation BT.601: Y = 0.299R + 0.587G + 0.114B 

        for r, row in enumerate(image): 

            for p, px in enumerate(row): 

                gray_image[r][p] = 0.2126 * px[0] + 0.7152 * px[1] + 0.0722 * px[2] 

 

        return np.round(gray_image, decimals=0).astype(int) 
</pre>


<h2 id="h-conclusion-nbsp" class="wp-block-heading">Conclusion&nbsp;</h2>



<p class="wp-block-paragraph">Image processing might sound and look like a daunting task, but I’ve always found it very gratifying. The algorithms require lots of math and in-depth understanding of calculus which sounds intimidating, but it doesn’t have to be.&nbsp;</p>



<p class="wp-block-paragraph">DMC would be happy to assist you with any software engineering endeavors utilizing years of experience in machine vision and Layer Imaging and Inspection Programming.&nbsp;</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Turn Image Processing Theory into Working Algorithms</strong></h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/manufacturing-automation-and-intelligence/" data-type="page" data-id="420">Automation</a> expertise in <a href="https://static.dmcinfo.com/services/manufacturing-automation-and-intelligence/vision-inspection/" data-type="page" data-id="494">machine vision</a>, image filtering, edge detection, and custom algorithm development.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/15899/image-processing-basics-and-how-to-code-your-own-algorithms/">Image Processing Basics and How to Code Your Own Algorithms</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Integrate Azure IoT Edge, .NET, OpenTelemetry Collector, and Application Insights</title>
		<link>https://static.dmcinfo.com/blog/15913/how-to-integrate-azure-iot-edge-net-opentelemetry-collector-and-application-insights/</link>
		
		<dc:creator><![CDATA[DMC]]></dc:creator>
		<pubDate>Fri, 25 Oct 2024 13:26:46 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[IoT]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/15913/how-to-integrate-azure-iot-edge-net-opentelemetry-collector-and-application-insights/</guid>

					<description><![CDATA[<p>All industries continue using the Internet-of-Things (IoT) to collect, monitor, and analyze data. One popular IoT option is using Azure IoTHub, which this tutorial will focus on.&#160; Connecting IoT Devices to an IoT Edge device allows for data processing to be even faster since an IoT Edge device allows you to analyze data closer to [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/15913/how-to-integrate-azure-iot-edge-net-opentelemetry-collector-and-application-insights/">How to Integrate Azure IoT Edge, .NET, OpenTelemetry Collector, and Application Insights</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">All industries continue using the Internet-of-Things (IoT) to collect, monitor, and analyze data. One popular IoT option is using Azure IoTHub, which this tutorial will focus on.&nbsp;</p>



<p class="wp-block-paragraph">Connecting IoT Devices to an <a href="https://learn.microsoft.com/en-us/azure/iot-edge/about-iot-edge?view=iotedge-1.4" target="_blank">IoT Edge</a> device allows for data processing to be even faster since an IoT Edge device allows you to analyze data closer to your IoT devices. This gives you the advantage of preprocessing data prior to sending it to the cloud. &nbsp;</p>



<p class="wp-block-paragraph">In this article, we will review how we can get data running on an ASP.NET application of a child device to an OpenTelemetry Collector module running on an IoT Edge device up into Application Insights in the Azure cloud. Since the application is running on a separate device than the device running the OpenTelemetry Collector module, this example can be used to send trace and metrics data to Application Insights when an IoT device does not have direct access to the internet but the IoT Edge device does.&nbsp;</p>



<p class="wp-block-paragraph"><a href="https://opentelemetry.io/docs/what-is-opentelemetry/" target="_blank">OpenTelemetry</a> is a framework and toolkit to manage telemetry data. It provides a protocol that specifies how telemetry should be formatted and set. OpenTelemetry is favorable to use since it is vendor and tool-agnostic and can be used with different <a href="https://opentelemetry.io/docs/concepts/observability-primer/#what-is-observability" target="_blank">Observability</a> backends such as <a href="https://www.jaegertracing.io/" target="_blank">Jaegar</a> and <a href="https://prometheus.io/" target="_blank">Prometheus</a>. Additionally, OpenTelemetry provides SDKs to implement clients in different languages, including .NET.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/OpenTelemetry-framework.png" alt="OpenTelemetry framework"/></figure>



<h2 id="h-prerequisites-nbsp" class="wp-block-heading">Prerequisites&nbsp;</h2>



<ul class="wp-block-list">
<li>Visual Studio 2022 Preview&nbsp;</li>



<li>Docker Desktop&nbsp;</li>



<li>Application Insights resource in Azure&nbsp;
 
 
<ul class="wp-block-list">
<li>Application Insights is not a totally free service, but Azure offers&nbsp;a <a href="https://azure.microsoft.com/en-us/pricing/offers/ms-azr-0044p" target="_blank">free account</a> that comes with $200 in Azure credits.</li>
</ul>
</li>



<li>IoT Edge device to deploy modules&nbsp;
 
 
<ul class="wp-block-list">
<li>The IoT Edge device will be used to deploy the OpenTelemetry Collector module. You can follow <a href="https://learn.microsoft.com/en-us/azure/iot-edge/how-to-visual-studio-develop-module?view=iotedge-1.4&amp;pivots=iotedge-dev-cli" target="_blank">this tutorial</a> to set up your IoT Edge device and example module.&nbsp;</li>
</ul>
</li>
</ul>



<h2 id="h-implement-the-opentelemtry-exporter-in-asp-net-application-nbsp" class="wp-block-heading">Implement the OpenTelemtry Exporter in ASP.NET Application&nbsp;</h2>



<p class="wp-block-paragraph">To get started, I created an ASP.NET Core Web App (Razor Pages) project with .NET 8.0 from the Visual Studio project templates.&nbsp;</p>



<p class="wp-block-paragraph">Next, we will add an <a href="https://opentelemetry.io/docs/languages/net/exporters/" target="_blank">OpenTelemetry exporter</a> to the service registration of our web application. There are a variety of exporters to choose from, but for this tutorial, we will use <a href="https://prometheus.io/" target="_blank">Prometheus</a> to export metrics and <a href="https://opentelemetry.io/docs/specs/otel/protocol/" target="_blank">OpenTelemetry Protocol (OTLP)</a> to export application logs.&nbsp;</p>



<p class="wp-block-paragraph">I chose Prometheus for metrics since it is highly reliable when recording numeric time series data, and it has an easy plug-in to use with C# and ASP.NET. I chose OTLP for logs since it also has an easy plug-in for ASP.NET and Prometheus does not support exporting traces.&nbsp;</p>



<p class="wp-block-paragraph">The Prometheus Exporter relies on the following packages:&nbsp;</p>



<ul class="wp-block-list">
<li><a href="https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.AspNetCore/1.7.0-rc.1" target="_blank">OpenTelemetry.Exporter.Prometheus.AspNetCore&nbsp;</a></li>



<li><a href="https://www.nuget.org/packages/OpenTelemetry.Extensions.Hosting" target="_blank">OpenTelemetry.Extensions.Hosting&nbsp;</a></li>
</ul>



<p class="wp-block-paragraph">The OTLP Exporter relies on the following packages:&nbsp;</p>



<ul class="wp-block-list">
<li><a href="https://www.nuget.org/packages/OpenTelemetry.Exporter.OpenTelemetryProtocol/" target="_blank">OpenTelemetry.Exporter.OpenTelemetryProtocol&nbsp;</a></li>
</ul>



<p class="wp-block-paragraph">Your Program.cs file should look like the following:&nbsp;</p>



<ol class="wp-block-list">
<li>Configure logging to log to the console and to the OpenTelemetry OTLP Exporter. The IP address used for the OTLP Exporter endpoint should be the IP address of the IoT Edge Device. The default <a href="https://opentelemetry.io/docs/specs/otel/protocol/exporter/">HTTP port is 4318 with the relative path of “v1/logs”</a> as described here.&nbsp;</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Program_cs-step-1.png" alt="PpenTelemetry exporter"/></figure>



<ol start="2" class="wp-block-list">
<li>Configure OpenTelemetry to use the Prometheus Exporter to export metrics. This example configures built-in metrics for “Microsoft.AspNetCore.Hosting” and “Microsoft.AspNet.Diagnostics.”&nbsp;It also configures metrics, for example, custom metric “HatCo.HatStore.”</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Program_cs-step-2_1.png" alt="Configure OpenTelemetry"/></figure>



<ol start="3" class="wp-block-list">
<li>Configure the web application.&nbsp;</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Program_cs-step-3.png" alt="Configure web app"/></figure>



<ol start="4" class="wp-block-list">
<li>Configure the web application to use the Prometheus scraping endpoint. Run background, test logic to generate logs and custom metric data for “HatCo.HatStore”. Run the application.&nbsp;</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Program_cs-step-4.png" alt="Prometheus scraping endpoint"/></figure>



<h2 id="h-add-the-opentelemetry-collector-module-to-your-iot-edge-device-nbsp" class="wp-block-heading">Add the OpenTelemetry Collector Module to your IoT Edge Device&nbsp;</h2>



<p class="wp-block-paragraph">We are going to test running the IoT Edge Modules in the simulator. This will allow the modules to run on your local Docker container registry.&nbsp;</p>



<p class="wp-block-paragraph">Within your IoT Edge VS project, we will edit the “deployment.template.json” file to add the OpenTelemetry Collector module. The “modules” section should look like the following example.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/modules-selection.png" alt="Add OpenTelemetry Collector module"/></figure>



<p class="wp-block-paragraph">The image name should match the desired image name when we create the OpenTelemetry Collector image in Build the Module. The $APPI_CONNECTION_STRING is an environment variable on your local computer that can be set in a .env file within the VS solution. APPI_CONNECTION_STRING sets up an environment variable within the OpenTelemetry Collector module, and this variable is used in the config.yaml file for configuring the module which I&#8217;ll&nbsp;detail in the next section. &nbsp;</p>



<p class="wp-block-paragraph">Once the deployment file is edited, right-click the Edge project and select “Generate Deployment for IoT Edge.&#8221;</p>



<h2 id="h-create-the-configuration-file-for-opentelemetry-collector" class="wp-block-heading">Create the Configuration File for OpenTelemetry Collector</h2>



<p class="wp-block-paragraph">Before running the OpenTelemetry Collector module on our IoT Edge device, we will want to configure how the OpenTelemetry Collector module will receive data from exporters in our ASP.NET application and configure how the OpenTelemetry Collector should export the data it receives. The <a href="https://opentelemetry.io/docs/collector/configuration/" target="_blank">configuration</a> for the module goes into a .yaml file. Your configuration file should look similar to the example below. You can add this config.yaml file into your IoT Edge solution that was created in the IoT Edge tutorial.&nbsp;</p>



<p class="wp-block-paragraph">In this example, we have configured the OpenTelemetry Collector module to use a Prometheus receiver to scrape metrics. The Prometheus receiver on the OpenTelemetry Collector module has to actively scrape the endpoint where the child device is running the ASP.NET application. We then use Azure Monitor to report those metrics to Application Insights. For the “scrape_configs” section, the “targets” are the endpoints where you would like to scrape metrics from. You can have multiple “scrape_configs”. Note that as configured, Prometheus will scrape only from HTTP endpoints. If your ASP.NET project uses different ports for HTTP and HTTPS, ensure to provide the HTTP port.&nbsp;</p>



<p class="wp-block-paragraph">We have also configured an OTLP receiver to retrieve logs. The default endpoint for the OTLP receiver using the HTTP protocol is<strong> https://static.dmcinfo.com/v1/logs</strong> which is what is configured for the OTLP exporter on the ASP.NET project. “Localhost” here references the Edge device. The logs are also exported to Application Insights via Azure Monitor. <em>Note: the &#8220;localhost&#8221; link is part of the tutorial and can only be accessed locally as part of running the tutorial itself.&nbsp;&nbsp;</em></p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/configure-OTLP-receive-logs.png" alt="Create the Configuration File for OpenTelemetry Collector "/></figure>



<h2 id="h-build-the-module-nbsp" class="wp-block-heading">Build the Module&nbsp;</h2>



<p class="wp-block-paragraph">To build the module in Docker Desktop, we will utilize the public <a href="https://hub.docker.com/r/otel/opentelemetry-collector-contrib" target="_blank">OpenTelemetry Collector module image</a>, and then we will copy our configuration file into it to modify the agent behavior. Create a Dockerfile to pull the OpenTelemetry Collector module image and copy over your configuration file. In the Dockerfile, we expose 4318 so that the ASP.NET application can push logs to our OTLP endpoint. The example Dockerfile below assumes that the Dockerfile and the config.yaml file are in the same directory.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/build-module-docker-desktop.png" alt="example Dockerfile "/></figure>



<p class="wp-block-paragraph">Within a command prompt, navigate to the directory where your Dockerfile.amd64 file lives. Run the following command to build and push your image to your local Docker container registry.&nbsp;</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">PowerShell</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>docker build -t localhost:5000/opentelemetrycollector:0.0.1-windows-amd64 -f Dockerfile.amd64 . </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">docker build -t localhost:</span><span style="color: #B5CEA8">5000</span><span style="color: #D4D4D4">/opentelemetrycollector:</span><span style="color: #B5CEA8">0.0</span><span style="color: #D4D4D4">.</span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">-windows-amd64 </span><span style="color: #D4D4D4">-f</span><span style="color: #D4D4D4"> Dockerfile.amd64 . </span></span></code></pre></div>



<h2 id="h-deploy-the-edge-device-and-collect-logs-and-metrics-nbsp" class="wp-block-heading">Deploy the Edge Device and Collect Logs and Metrics&nbsp;</h2>



<p class="wp-block-paragraph">After generating your deployment file for the Edge device, right-click the Edge project and select “Build and Run IoT Edge Modules in Simulator.”&nbsp;This will run the OpenTelemetry Collector module from your Edge device on your local instance of Docker Desktop. You should see the logs for the OpenTelemetry Collector module trying to scrape your application metrics.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/deploy-edge-device.png" alt="deploy edge device"/></figure>



<h2 id="h-view-metrics-and-logs-in-application-insights-nbsp" class="wp-block-heading">View Metrics and Logs in Application Insights&nbsp;</h2>



<p class="wp-block-paragraph">Once the Edge device is running, run your ASP.NET project. Navigate to your Application Insights resource in Azure Portal. In the left-hand navigation bar within the Application Insights resource under Monitor, select Logs. Query the logs for “traces” and “customMetrics.”&nbsp;&nbsp;</p>



<p class="wp-block-paragraph">Under traces, you will see your application logs appear.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/application-logs-appear.png" alt="application logs appear"/></figure>



<p class="wp-block-paragraph">Under “customMetrics”, you will see your application metrics which includes the example metric “hat_sold_Hats_total.&#8221;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/application-metrics-with-example.png" alt="application metrics with example"/></figure>



<h2 id="h-conclusion-nbsp" class="wp-block-heading">Conclusion&nbsp;</h2>



<p class="wp-block-paragraph">With this tutorial as an example, you now have the ability to collect metrics and logs via an IoT Edge device from multiple applications!&nbsp;</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Extend Application Observability to the IoT Edge</strong>.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in Azure IoT Edge, .NET, OpenTelemetry, and Application Insights for advanced <a href="https://static.dmcinfo.com/services/embedded-development-and-embedded-programming/iot-solutions/" data-type="page" data-id="704">IoT capabilities</a>.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/15913/how-to-integrate-azure-iot-edge-net-opentelemetry-collector-and-application-insights/">How to Integrate Azure IoT Edge, .NET, OpenTelemetry Collector, and Application Insights</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Basic JavaScript Localization Setup Using JSON with Visual Studio Code</title>
		<link>https://static.dmcinfo.com/blog/15997/basic-javascript-localization-setup-using-json-with-visual-studio-code/</link>
		
		<dc:creator><![CDATA[Shannon Coupland]]></dc:creator>
		<pubDate>Mon, 16 Sep 2024 12:55:30 +0000</pubDate>
				<category><![CDATA[Web Application Development]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/15997/basic-javascript-localization-setup-using-json-with-visual-studio-code/</guid>

					<description><![CDATA[<p>Frontend localization is different than .NET backend localization in that there is not an established “correct way” to accomplish translating text in JS and related frameworks. This is a basic example using JavaScript, but more customization can be done with methods and helpers for accessing text, especially if you are using a JS framework like [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/15997/basic-javascript-localization-setup-using-json-with-visual-studio-code/">Basic JavaScript Localization Setup Using JSON with Visual Studio Code</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Frontend localization is different than .NET backend localization in that there is not an established “correct way” to accomplish translating text in JS and related frameworks. This is a basic example using JavaScript, but more customization can be done with methods and helpers for accessing text, especially if you are using a JS framework like React or Material UI.</p>



<p class="wp-block-paragraph"><strong>Prerequisites</strong></p>



<ul class="wp-block-list">
<li>Visual Studio Code installed on your machine</li>



<li>Node.js version 20.17.0 installed on your machine (other versions of Node will likely work as well)</li>



<li>General understanding of navigation in Visual Studio Code IDE</li>
</ul>



<p class="wp-block-paragraph"><strong>Steps</strong></p>



<ol class="wp-block-list">
<li>Open Visual Studio Code and create an empty folder “LocalizationExampleJS”:<br>
 <br>
 <img decoding="async" alt="Open Visual Studio Code" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-JavaScript-1.png"><br>
 &nbsp;</li>



<li value="2">Add the file &ldquo;LocalizationExample.js&rdquo; at the top level of your new folder:<br />
 <br />
 <img decoding="async" alt="" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-JavaScript-2.png"  /><br />
 &nbsp;</li>



<li value="3">Add a new subfolder &quot;Resources&quot;&nbsp;with empty files &ldquo;en-US.json&rdquo;, &ldquo;es-MX.json&rdquo;, and &ldquo;fr-FR.json&rdquo;:<br />
 <br />
 <img decoding="async" alt="" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-JavaScript-3.png"  /><br />
 <br />
 <img decoding="async" alt="" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-JavaScript-4.png"  /></li>



<li>Add the following code to each JSON translation file. It’s important for the text keys in all 3 files to be identical – in this case, the sample text we are using has the key “HelloWorld”.</li>
</ol>



<p class="wp-block-paragraph"><code><strong>en-US.json</strong></code></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JSON</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;code>
{
  "HelloWorld": "Hello World!"
}
es-MX.json
{
  "HelloWorld": "¡Hola Mundo!"
}
fr-FR.json
{
  "HelloWorld": "Bonjour le monde!"
}
&lt;/code></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">&lt;code&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">&quot;HelloWorld&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;Hello World!&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">es-MX.json</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">&quot;HelloWorld&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;¡Hola Mundo!&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">fr-FR.json</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">&quot;HelloWorld&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;Bonjour le monde!&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">&lt;/code&gt;</span></span></code></pre></div>



<ol class="wp-block-list">
<li value="5">Add the following code to &ldquo;LocalizationExample.js&rdquo;. This will enable you to supply a command line argument containing your desired locale string during testing. (Note that in an actual web frontend solution, the frontend code would need to access the current user&rsquo;s culture from the server, NOT via command line argument.)</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;code>
const args = process.argv.slice(2);
const currentCulture = args&#91;0&#93;;
console.log("Your supplied culture is: " + currentCulture);
&lt;/code></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #808080">&lt;</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">const args = process.argv.slice(2);</span></span>
<span class="line"><span style="color: #D4D4D4">const currentCulture = args&#91;0&#93;;</span></span>
<span class="line"><span style="color: #D4D4D4">console.log(&quot;Your supplied culture is: &quot; + currentCulture);</span></span>
<span class="line"><span style="color: #808080">&lt;/</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span></code></pre></div>



<ol class="wp-block-list">
<li value="6">Below the code to extract current culture from command line arguments, add the following code to access the &ldquo;HelloWorld&rdquo; string for the supplied culture. If a culture is supplied that is not present in the application, then an error message will log accordingly, and the application will default to the &ldquo;en-US&rdquo; locale.</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;code>
var pathToJson = "./Resources/" + args&#91;0&#93; + ".json";
try {
  const jsonObj = require(pathToJson);
  console.log(jsonObj.HelloWorld);
}
catch (err) {
  console.log("Culture \"" + currentCulture + "\" does not exist in this application - using default locale en-US.")
  pathToJson = "./Resources/en-US.json";
  const jsonObj = require(pathToJson);
  console.log(jsonObj.HelloWorld);
}
&lt;/code></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #808080">&lt;</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">var pathToJson = &quot;./Resources/&quot; + args&#91;0&#93; + &quot;.json&quot;;</span></span>
<span class="line"><span style="color: #D4D4D4">try </span><span style="color: #569CD6">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">const</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">require</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">HelloWorld</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">catch (err) </span><span style="color: #569CD6">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;Culture </span><span style="color: #D7BA7D">\&quot;</span><span style="color: #CE9178">&quot;</span><span style="color: #D4D4D4"> + </span><span style="color: #9CDCFE">currentCulture</span><span style="color: #D4D4D4"> + </span><span style="color: #CE9178">&quot;</span><span style="color: #D7BA7D">\&quot;</span><span style="color: #CE9178"> does not exist in this application - using default locale en-US.&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&quot;./Resources/en-US.json&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">const</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">require</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">HelloWorld</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #808080">&lt;/</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span></code></pre></div>



<ol class="wp-block-list">
<li value="7">Your code is ready for testing! It should look something like this:</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;code>
const args = process.argv.slice(2);
const currentCulture = args&#91;0&#93;;
console.log("Your supplied culture is: " + currentCulture);

var pathToJson = "./Resources/" + args&#91;0&#93; + ".json";
try {
  const jsonObj = require(pathToJson);
  console.log(jsonObj.HelloWorld);
}
catch (err) {
  console.log("Culture \"" + currentCulture + "\" does not exist in this application - using default locale en-US.")
  pathToJson = "./Resources/en-US.json";
  const jsonObj = require(pathToJson);
  console.log(jsonObj.HelloWorld);
}
&lt;/code></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #808080">&lt;</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">const args = process.argv.slice(2);</span></span>
<span class="line"><span style="color: #D4D4D4">const currentCulture = args&#91;0&#93;;</span></span>
<span class="line"><span style="color: #D4D4D4">console.log(&quot;Your supplied culture is: &quot; + currentCulture);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">var pathToJson = &quot;./Resources/&quot; + args&#91;0&#93; + &quot;.json&quot;;</span></span>
<span class="line"><span style="color: #D4D4D4">try </span><span style="color: #569CD6">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">const</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">require</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">HelloWorld</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">catch (err) </span><span style="color: #569CD6">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;Culture </span><span style="color: #D7BA7D">\&quot;</span><span style="color: #CE9178">&quot;</span><span style="color: #D4D4D4"> + </span><span style="color: #9CDCFE">currentCulture</span><span style="color: #D4D4D4"> + </span><span style="color: #CE9178">&quot;</span><span style="color: #D7BA7D">\&quot;</span><span style="color: #CE9178"> does not exist in this application - using default locale en-US.&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">&quot;./Resources/en-US.json&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">const</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">require</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">pathToJson</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">jsonObj</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">HelloWorld</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #808080">&lt;/</span><span style="color: #569CD6">code</span><span style="color: #808080">&gt;</span></span></code></pre></div>



<ol class="wp-block-list">
<li value="8">Open a terminal and test out the LocalizationExample solution using the command node <strong><code>.\LocalizationExample.js</code></strong> xx-XX, where &ldquo;xx-XX&rdquo; is a locale string representing the combination of a language and a region. Try out &ldquo;es-MX&rdquo;, &ldquo;fr-FR&rdquo;, &ldquo;en-US&rdquo;, and &ldquo;pt-BR&rdquo; (Brazilian Portuguese) as command line arguments.<br />
 <br />
 Note that the HelloWorld resource for the cultures &ldquo;en-US&rdquo;, &ldquo;es-MX&rdquo;, and &ldquo;fr-FR&rdquo; all appear translated as expected. However, the &ldquo;pt-BR&rdquo; locale yields the HelloWorld string in English, because this culture is not present in the collection of translations, and our code explicitly defaults to the &ldquo;en-US&rdquo; U.S. English locale in this case.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-JavaScript-6.png" alt=""/></figure>



<h2 id="h-localization-tips-and-tricks" class="wp-block-heading"><strong>Localization Tips and Tricks</strong></h2>



<p class="wp-block-paragraph">When it comes to localization, there is a lot more to consider than simply translating text and accessing it appropriately from code. Read on to learn some tips and tricks for elevating your localization solution!</p>



<p class="wp-block-paragraph"><strong>Text Key Organization</strong></p>



<p class="wp-block-paragraph">Generally, DMC has found that it is beneficial to organize text keys into separate files based not only on language, but also based on what page of your application the text belongs on. For backend localization, this is likely best done by splitting text by controller. Here is an example of a backend localization file structure that includes two controllers, “Login.cs” and “Home.cs”, and two cultures, a default culture and “es-MX”:</p>



<ul class="wp-block-list">
<li>Resources folder
 
 
<ul class="wp-block-list" style="list-style-type:circle;">
  

<li>Login folder
  

<ul class="wp-block-list">
   

<li>Login.resx</li>


   

<li>Login.es-MX.resx</li>


  </ul>


  </li>


  

<li>Home folder
  

<ul class="wp-block-list">
   

<li>Home.resx</li>


   

<li>Home.es-MX.resx</li>


  </ul>


  </li>


 </ul>
</li>
</ul>



<p class="wp-block-paragraph">This separation organizes your translated text in a way that is easily modifiable for each individual page of your application.</p>



<p class="wp-block-paragraph">In the event that identical text appears in multiple places in your application in English, it is still beneficial to duplicate that text key/value entry across pages, rather than maintaining a single key/value item. For example, if the word “Email” exists on both the “Login” and “Home” pages in the example above, this text key/value should exist in both the “Login” resource files and “Home” resource files. This enables you to update the text in one place without modifying the same text elsewhere, if desired. Also, translations of the same English word or phrase could be different across different languages, depending on the context of how the word or phrase is used – in this case, separate text/key values are necessary for translators to appropriately translate your application text.</p>



<p class="wp-block-paragraph"><strong>Variables and Pluralization</strong></p>



<p class="wp-block-paragraph">Variables and pluralization can be quite tricky to handle in any localization solution. For a backend solution with .resx files, if you have a sentence to translate with a variable in the middle, you need to separate any given string key/value into separate parts that precede and follow the variable.</p>



<p class="wp-block-paragraph">For example, say you want to include the phrase “Hello, {name} – welcome to my site!” The following key/value structure would need to be implemented in each .resx file to handle this phrase:</p>


<table border="1" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width:319px;">


<p class="wp-block-paragraph"><strong>Key</strong></p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph"><strong>Value (en-US)</strong></p>



<p class="wp-block-paragraph">Hello_BeforeName</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“Hello, “</p>



<p class="wp-block-paragraph">Hello_AfterName</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“ – welcome to my site!”</p>


</td>
</tr>
</tbody>
</table>


<p class="wp-block-paragraph">&nbsp;</p>



<p class="wp-block-paragraph">Additionally, numeric variables add an extra layer of complexity to a localization solution, because pluralization must be implemented for these. The way words are pluralized can actually differ significantly across languages. For example, Slavic languages (e.g. Russian, Czech) have a different style of pluralization for 5 or more of an item than they do for zero, one, or 2-4 items. So a solution that translates to Slavic languages must implement separate logic for pluralizing 5+ items than for other items. For example, the sentence “You have {number} items in your cart!” may have a key/value structure like this in English:</p>


<table border="1" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width:319px;">


<p class="wp-block-paragraph"><strong>Key</strong></p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph"><strong>Value (en-US)</strong></p>



<p class="wp-block-paragraph">ItemsInCart_BeforeValue_0</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“You have “</p>



<p class="wp-block-paragraph">ItemsInCart_AfterValue_0</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“ items in your cart!”</p>



<p class="wp-block-paragraph">ItemsInCart_BeforeValue_1</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“You have “</p>



<p class="wp-block-paragraph">ItemsInCart_AfterValue_1</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“ item in your cart!”</p>



<p class="wp-block-paragraph">ItemsInCart_BeforeValue_2to4</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“You have “</p>



<p class="wp-block-paragraph">ItemsInCart_AfterValue_2to4</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“ items in your cart!”</p>



<p class="wp-block-paragraph">ItemsInCart_BeforeValue_5up</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“You have “</p>



<p class="wp-block-paragraph">ItemsInCart_AfterValue_5up</p>


</td>
<td style="width:319px;">


<p class="wp-block-paragraph">“ items in your cart!”</p>


</td>
</tr>
</tbody>
</table>


<p class="wp-block-paragraph">As you can see, this is a complex case to translate for – there are 8 total key/value pairs to accommodate just one sentence that integrates a numeric variable. To avoid the need for this, one solution is to reduce or eliminate the need for accommodating plurals – for example, you could rephrase “You have {number} items in your cart!” to instead become “Number of Items in Cart: {number}”. This would only require one key/value pair to be translated instead of eight.</p>



<p class="wp-block-paragraph"><strong>Fallback Cultures</strong></p>



<p class="wp-block-paragraph">When a user’s specific locale does not have a translation present in your application, it can be beneficial to implement fallback cultures so this user is given a more suitable approximation of their desired language. For example, say a Spanish speaker lives in Spain and therefore has their browser locale set to “es-ES”. However, your application only has resources for Mexican Spanish “es-MX”. Ideally this person in Spain would see the application in Mexican Spanish rather than the application default of English, because Mexican Spanish more closely matches their desired language than English does.</p>



<p class="wp-block-paragraph">Using .NET backend localization, fallback cultures can be implemented by simply renaming any files ending in “es-MX.resx” to instead end in “es.resx”. Per .NET’s <a href="#culture-fallback-scenarios">culture fallback scenarios</a>, upon receiving a request from the Spain Spanish user, .NET will still search for “es-ES” first. But upon not finding this specific culture, .NET will “fall back” to just the “es” portion of the locale string and will access the text as it exists in this new general Spanish file. Users using Mexican Spanish “es-MX” will see the same behavior.</p>



<p class="wp-block-paragraph"><strong>Common Pitfalls</strong></p>



<p class="wp-block-paragraph">Localization can be complicated, and there are some common pitfalls you may encounter if you are implementing your own localization solution. Here are some examples:</p>



<ul class="wp-block-list">
<li><strong>Logging / error messaging:</strong> if you are displaying raw exception messages to your users, then these are not able to be localized because they are generally hardcoded in English by the entities providing the error messages. In this case, your application should be updated to display custom error messages to your users instead of raw messages.</li>



<li><strong>Identifying EVERY source of user-facing text:</strong> There are many possible sources of text in your application that can go unnoticed, which can then become difficult to handle late in the localization process. Some examples are images, email templates, and messages from external libraries/packages/APIs. If you are doing frontend only localization, then it becomes necessary to make 100% certain that there is NO user facing text coming from the backend – if you discover late in the process that some user facing text comes from the server, then some “hacky” solutions may be necessary to preserve the frontend-only localization scheme.</li>



<li><strong>Not enforcing a single source of truth for text:</strong> If there is not a very solid system in place for establishing and updating the “source or truth” for translated text, then the localization process can quickly become messy. Things are always changing during development, and if there is not an established system to deliver text updates to translators and other stakeholders, then any one party can modify text without other stakeholders’ knowledge and introduce confusion. Enforce a single source of truth for text that all stakeholders can agree on. (Resource files are likely NOT the appropriate single source of truth, since non-developer stakeholders should also have control over text in the application.)</li>
</ul>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Build Applications That Speak Your Users’ Language</strong>.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Check out our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> solutions for JavaScript localization, multilingual interfaces, and scalable translation workflows with Visual Studio Code.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/15997/basic-javascript-localization-setup-using-json-with-visual-studio-code/">Basic JavaScript Localization Setup Using JSON with Visual Studio Code</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Basic Localization Setup in .NET 8 with Visual Studio</title>
		<link>https://static.dmcinfo.com/blog/16011/basic-localization-setup-in-net-8-with-visual-studio/</link>
		
		<dc:creator><![CDATA[Shannon Coupland]]></dc:creator>
		<pubDate>Mon, 16 Sep 2024 10:25:01 +0000</pubDate>
				<category><![CDATA[Web Application Development]]></category>
		<category><![CDATA[Localization Setup]]></category>
		<category><![CDATA[Microsoft Visual Studio]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/16011/basic-localization-setup-in-net-8-with-visual-studio/</guid>

					<description><![CDATA[<p>Localization as it pertains to software is a very broad topic, but generally, to “localize” an application means to write or update the user-facing components of an application to simultaneously target users from multiple regions, languages, and/or cultures. Often, software engineers refer to localization to mean simply translating an application into multiple languages. But in [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16011/basic-localization-setup-in-net-8-with-visual-studio/">Basic Localization Setup in .NET 8 with Visual Studio</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Localization as it pertains to software is a very broad topic, but generally, to “localize” an application means to write or update the user-facing components of an application to simultaneously target users from multiple regions, languages, and/or cultures.</p>



<p class="wp-block-paragraph">Often, software engineers refer to localization to mean simply translating an application into multiple languages. But in reality, localization can be much more involved than basic text translation. When localizing an application, these can all be items a software engineer needs to consider:</p>



<ul class="wp-block-list">
<li>Translation of user-facing text</li>



<li>Date formatting</li>



<li>Currency formatting</li>



<li>Number formatting</li>



<li>Text ordering and direction</li>



<li>Images and other content</li>



<li>Regional language differences</li>



<li>And others</li>
</ul>



<p class="wp-block-paragraph">Every localization system is different, but in general, localization solutions should NOT solely consider target languages. Rather, to target a specific group of users, <em>locales</em> should be used. A locale combines <em>language</em> (what language the target users speak) with <em>region</em> (the area of the world in which the target users live). Region and language can be combined to form a <em>locale string</em>, generally in the format “xx-XX”, where “xx” is a two-letter code indicating the <em>language</em> and “XX” is a two-letter code indicating the <em>region</em>. Here are some examples of locale strings:</p>



<ul class="wp-block-list">
<li>en-US: English as spoken in the United States</li>



<li>en-GB: English as spoken in United Kingdom of Great Britain and Northern Ireland</li>



<li>pt-BR: Portuguese as spoken in Brazil</li>



<li>pt-PL: Portuguese as spoken in Portugal</li>



<li>es-MX: Spanish as spoken in Mexico</li>



<li>es-US: Spanish as spoken in the United States</li>
</ul>



<p class="wp-block-paragraph">Language abbreviations are standardized using the <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO 639-1 language codes</a>, and country abbreviations are standardized using the <a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha-2 country codes</a>. A combination of codes from these two lists forms a complete locale, used to uniquely identify resources used for a language and region.</p>



<p class="wp-block-paragraph">Successfully implementing an efficient and maintainable localization system involves significant upfront planning, for both software solution structure and communication with text stakeholders and translators.</p>



<h2 class="wp-block-heading" id="h-basic-localization-setup-in-net-8-with-visual-studio"><strong>Basic Localization Setup in .NET 8 with Visual Studio</strong></h2>



<p class="wp-block-paragraph">This section will demonstrate how to set up basic localization in .NET 8 and Visual Studio Code. We will create a .NET 8 solution, and then we will modify the solution to include localization for two additional cultures (Spanish as spoken in Mexico “es-MX” and French as spoken in France “fr-FR”).</p>



<p class="wp-block-paragraph">Note that programmatically, Microsoft uses the terminology “culture” rather than “locale” to indicate a combination of region and language that an application must be localized to.</p>



<p class="wp-block-paragraph"><strong>Prerequisites</strong></p>



<ul class="wp-block-list">
<li>Visual Studio 2022 installed on your machine</li>



<li>.NET 8.0 SDK installed on your machine</li>



<li>General understanding of navigation for Visual Studio IDE</li>
</ul>



<p class="wp-block-paragraph"><strong>Localization Steps</strong></p>



<ol class="wp-block-list">
<li>In Visual Studio 2022, create a new .NET 8 console application named <strong><code>LocalizationExample</code></strong>.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-1.png" alt="Create a new .NET 8 console application"/></figure>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-2.png" alt="create a new .NET 8 console application named LocalizationExample"/></figure>



<p class="wp-block-paragraph">Once the project is created and opened in Visual Studio, it will look like this:</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-3.png" alt="Created project viewed in Visual Studio"/></figure>



<ol class="wp-block-list">
<li value="2">In the Visual Studio Package Manager Console, run the following commands to install necessary packages:
 

<ol class="wp-block-list" style="list-style-type:lower-alpha;">
  

<li><strong><code>Install-Package Microsoft.Extensions.Hosting</code></strong></li>


  

<li><strong><code>Install-Package Microsoft.Extensions.DependencyInjection</code></strong></li>


  

<li><strong><code>Install-Package Microsoft.Extensions.Localization</code></strong></li>


 </ol>


 </li>
</ol>



<ol class="wp-block-list">
<li value="3">To set up localization with dependency injection, replace the contents of <strong><code>Program.cs</code></strong> with the following lines of code, which add the services required for localizing your application. Note that the specified <strong><code>ResourcePath</code></strong> is &ldquo;Resources&rdquo; &ndash; you will add this path to your project in the next step.</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">C#</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddLocalization(options =>
        {
            options.ResourcesPath = "Resources";
        });
    })
    .Build();
Console.WriteLine("Hello, World!");</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">using</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Microsoft</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">Extensions</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">Hosting</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #C586C0">using</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Microsoft</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">Extensions</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">DependencyInjection</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">using</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">IHost</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">host</span><span style="color: #D4D4D4"> = </span><span style="color: #4EC9B0">Host</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">CreateDefaultBuilder</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">args</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    .</span><span style="color: #4EC9B0">ConfigureServices</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">services</span><span style="color: #D4D4D4"> =&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #4EC9B0">services</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">AddLocalization</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">options</span><span style="color: #D4D4D4"> =&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #4EC9B0">options</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">ResourcesPath</span><span style="color: #D4D4D4"> = &quot;</span><span style="color: #4EC9B0">Resources</span><span style="color: #D4D4D4">&quot;;</span></span>
<span class="line"><span style="color: #D4D4D4">        });</span></span>
<span class="line"><span style="color: #D4D4D4">    })</span></span>
<span class="line"><span style="color: #D4D4D4">    .</span><span style="color: #DCDCAA">Build</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #9CDCFE">Console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">WriteLine</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;Hello, World!&quot;</span><span style="color: #D4D4D4">);</span></span></code></pre></div>



<p class="wp-block-paragraph">&nbsp;</p>



<ol class="wp-block-list">
<li value="4">In the <strong><code>LocalizationExample</code></strong> project, add a new folder called &ldquo;Resources&rdquo;. This directory is where the localization services are set up to search for your localization resource files that will contain your localized text.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-5.png" alt="Add a new folder called Resources"/></figure>



<ol class="wp-block-list">
<li value="5">In the &ldquo;Resources&rdquo; folder, add a new item using the &ldquo;Resources&rdquo; type template. Name it &ldquo;SampleResource.resx&rdquo;.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-6.png" alt="Add a new item using the &quot;Resources&quot; type template"/></figure>



<p class="wp-block-paragraph">When “SampleResource.resx” opens, you should see a table open with the columns “Name”, “Value”, and “Comment”. You will also see a dropdown at the top of the file with “Strings” selected – for this exercise you will preserve the “Strings” option, but other resource types can be localized by changing this dropdown. The resource file columns serve the following purposes:</p>



<ol class="wp-block-list" style="list-style-type:lower-alpha;">
 

<li>Name: a string that uniquely identifies the piece of text the entry contains in the.resx files preceded with &ldquo;SampleResource&rdquo;. Note that it cannot begin with a number and cannot contain spaces or most special characters.</li>


 

<li>Value: The string value that corresponds to the Name key for the culture associated with this resource file.</li>


 

<li>Comment: An optional text entry to clarify information about the resource entry.</li>


</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-7.png" alt="View of the “SampleResource.resx” table"/></figure>



<ol class="wp-block-list">
<li value="6">Repeat step 5 to create files &ldquo;SampleResource.es-MX.resx&rdquo; and &ldquo;SampleResource.fr-FR.resx&rdquo;.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-8.png" alt="Repeat the steps to create additional localization files"/></figure>



<ol class="wp-block-list">
<li value="7">Add the following entries to the .resx files created in steps 5-6. These values are &ldquo;Hello World!&rdquo; in different languages. Note that the file SampleResource.resx is the <em>default</em> resource file &ndash; if a culture is selected that does NOT match &ldquo;fr-FR&rdquo; or &ldquo;es-MX&rdquo; when the resource is accessed, then the application will default to the English value stored in SampleResource.resx.</li>
</ol>


<table border="1" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="width:153px;">


<p class="wp-block-paragraph">File</p>


</td>
<td style="width:142px;">


<p class="wp-block-paragraph">Name</p>


</td>
<td style="width:139px;">


<p class="wp-block-paragraph">Value</p>


</td>
<td style="width:141px;">


<p class="wp-block-paragraph">Comment</p>





<p class="wp-block-paragraph">SampleResource.resx</p>


</td>
<td style="width:142px;">


<p class="wp-block-paragraph">HelloWorld</p>


</td>
<td style="width:139px;">


<p class="wp-block-paragraph">Hello World!</p>


</td>
<td style="width:141px;">


<p class="wp-block-paragraph">Hello World in English</p>





<p class="wp-block-paragraph">SampleResource.es-MX.resx</p>


</td>
<td style="width:142px;">


<p class="wp-block-paragraph">HelloWorld</p>


</td>
<td style="width:139px;">


<p class="wp-block-paragraph">¡Hola Mundo!</p>


</td>
<td style="width:141px;">


<p class="wp-block-paragraph">Hello World in Spanish (MX)</p>





<p class="wp-block-paragraph">SampleResource.fr-FR.resx</p>


</td>
<td style="width:142px;">


<p class="wp-block-paragraph">HelloWorld</p>


</td>
<td style="width:139px;">


<p class="wp-block-paragraph">Bonjour le monde!</p>


</td>
<td style="width:141px;">


<p class="wp-block-paragraph">Hello World in French (FR)</p>


</td>
</tr>
</tbody>
</table>


<p class="wp-block-paragraph">&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-9.png" alt="Add entries for different languages"/></figure>



<ol class="wp-block-list">
<li value="8">Add the following code to Program.cs under the existing usings. This will enable the desired culture to be supplied as a command line argument, as well as add the appropriate using to use our new SampleResource files:</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">C#</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>using System.Globalization; 
if (args is { Length: 1 })
{
    CultureInfo.CurrentCulture =
        CultureInfo.CurrentUICulture =
            CultureInfo.GetCultureInfo(args&#91;0&#93;);
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">using</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">System</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">Globalization</span><span style="color: #D4D4D4">; </span></span>
<span class="line"><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">args</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">is</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">Length</span><span style="color: #D4D4D4">: </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4"> })</span></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">CultureInfo</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">CurrentCulture</span><span style="color: #D4D4D4"> =</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">CultureInfo</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">CurrentUICulture</span><span style="color: #D4D4D4"> =</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">CultureInfo</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">GetCultureInfo</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">args</span><span style="color: #D4D4D4">&#91;</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">&nbsp;</p>



<ol class="wp-block-list">
<li value="9">In Program.cs, replace the line <strong><code>Console.WriteLine(<span style="color:#cc0000;">&quot;Hello, World!&quot;</span>);</code></strong> with <strong><code>Console.WriteLine(SampleResource.HelloWorld);</code></strong>. Your Program.cs should now look like this:</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-10.png" alt="View of Program.cs after replacing code"/></figure>



<ol class="wp-block-list">
<li value="10">Build the solution by right clicking on the solution in the Solution Explorer and clicking &ldquo;Build Solution&rdquo;.</li>
</ol>



<ol class="wp-block-list">
<li value="11">Open the .NET CLI / Terminal through the View menu.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-11.png" alt="Open the .NET CLI / Terminal through the View menu"/></figure>



<ol class="wp-block-list">
<li value="12">Finally, test out the LocalizationExample solution via the newly opened terminal using the command dotnet run &#8211;project LocalizationExample.csproj xx-XX, where &ldquo;xx-XX&rdquo; is a combination of a language and a region. Try out &ldquo;es-MX&rdquo;, &ldquo;fr-FR&rdquo;, &ldquo;en-US&rdquo; (U.S. English), and &ldquo;pt-BR&rdquo; (Brazilian Portuguese) as command line arguments.</li>
</ol>



<p class="wp-block-paragraph">Note that the HelloWorld resource for the cultures “es-MX” and “fr-FR” both appear translated as expected. When .NET reaches into the Resources folder to obtain the “HelloWorld” resource, it uses the culture string associated with the stored CultureInfo.CurrentCulture to access the appropriate .resx file. However, the “en-US” and “pt-BR” cultures both yield the HelloWorld string in English, because neither of these cultures are explicitly provided for the SampleResource resource, and so .NET defaults to the base SampleResource.resx.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Localization-.Net-12.png" alt="Test out the LocalizationExample solution"/></figure>



<p class="wp-block-paragraph">DMC is a full-service <a href="https://static.dmcinfo.com/services/application-development">application development</a> company focused on custom web, mobile, cloud, and desktop applications.&nbsp; Contact us <a href="https://static.dmcinfo.com/contact#get-in-touch">today</a> to discuss your application development needs.</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16011/basic-localization-setup-in-net-8-with-visual-studio/">Basic Localization Setup in .NET 8 with Visual Studio</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Why DMC is Adopting Next.js for Web Application Development</title>
		<link>https://static.dmcinfo.com/blog/16026/why-dmc-is-adopting-next-js-for-web-application-development/</link>
		
		<dc:creator><![CDATA[Christopher Olsen]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 15:40:27 +0000</pubDate>
				<category><![CDATA[Web Application Development]]></category>
		<category><![CDATA[Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/16026/why-dmc-is-adopting-next-js-for-web-application-development/</guid>

					<description><![CDATA[<p>Introduction For the last seven&#160;years, DMC&#8217;s go-to stack for web application development has been a React SPA (single-page application) front-end with an ASP.NET Core REST API. Although that will still be a good fit for some projects,&#160;DMC has chosen to expand our toolset and adopt Next.js for developing new web applications. Why choosing the right [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16026/why-dmc-is-adopting-next-js-for-web-application-development/">Why DMC is Adopting Next.js for Web Application Development</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<ul class="wp-block-list">
<li><a href="#introduction">Introduction</a></li>



<li><a href="#overview-of-nextjs">Overview of Next.js</a></li>



<li><a href="#performance-improvements">Performance Improvements</a></li>



<li><a href="#developer-experience">Developer Experience</a></li>



<li><a href="#case-studies-and-success-stories">Case Studies and Success Stories</a></li>



<li><a href="#future-outlook">Future Outlook</a></li>



<li><a href="#conclusion">Conclusion</a></li>
</ul>



<h2 id="h-introduction" class="wp-block-heading"><a name="introduction">Introduction</a></h2>



<p class="wp-block-paragraph">For the last seven&nbsp;years, DMC&#8217;s go-to stack for web application development has been a React SPA (single-page application) front-end with an ASP.NET Core REST API. Although that will still be a good fit for some projects,&nbsp;DMC has chosen to expand our toolset and adopt Next.js for developing new web applications.</p>



<h3 id="h-why-choosing-the-right-framework-is-important" class="wp-block-heading">Why choosing the right framework is important</h3>



<p class="wp-block-paragraph">When developing a new web application, your choice of framework or tech stack can have enormous and wide-ranging implications, and the benefits and consequences of this decision can impact your business years later. These implications include, but are not limited to:</p>



<ul class="wp-block-list">
<li><strong>Performance.</strong> Page load times and smoothness of transitions in the UI can both be affected by your framework choice.</li>



<li><strong>Maintenance.</strong> Choosing an out-dated framework, or even choosing a modern framework that&#8217;s a poor fit for your application, can result in future feature requests taking much longer to implement. I.e. a 1-hour development task could be an 8-hour task in extreme cases.</li>



<li><strong>Scalability.</strong> Some frameworks can limit your options for scaling as your userbase groups. For example a Blazor Server application may make it more difficult to do horizontal scaling. Also, some ORMs will have issues once your data reaches a certain scale.</li>



<li><strong>Talent.</strong> If you choose an unpopular framework, or continue holding onto an outdated one, it can be difficult to hire, train, and retain good developer talent. After all, not many developers want to work on a no-framework PHP app or a ASP.NET Web Forms app in 2024.</li>



<li><strong>Support.</strong> This is where popularity comes into play, because it tends to be easy to find information about how to solve a specific problem with a popular framework, whereas trying to find information on how to solve a specific problem with an <em>unpopular</em> framework can be nearly impossible.</li>
</ul>



<p class="wp-block-paragraph">So when developing a new web application, you should look for a framework that is:</p>



<ul class="wp-block-list">
<li>Performant, either in general or at least for your use case</li>



<li>Has a good developer experience. This helps make maintenance easier and helps attract and retain talent.</li>



<li>Scalable.</li>



<li>Popular, or at the very least not <em>unpopular</em>.</li>
</ul>



<p class="wp-block-paragraph">With those points in mind, DMC has chosen to add <a href="https://nextjs.org/" target="_blank">Next.js</a> to our web development toolbelt and officially adopt it for use on new projects. We have done several pilot projects with Next.js and we feel it&#8217;s a good, balanced approach that will serve a majority of projects well.</p>



<h2 id="h-overview-of-next-js" class="wp-block-heading"><a name="overview-of-nextjs">Overview of Next.js</a></h2>



<h3 id="h-what-is-next-js" class="wp-block-heading">What is Next.js?</h3>



<p class="wp-block-paragraph">Next.js is a full-stack React framework that has actually been around a long time (1.0 release was in 2016), but its popularity and their profile in the React community has exploded since they released their big App Router update last year (May 2023). This version of Next.js became the first React framework to implement <a href="https://react.dev/reference/rsc/server-components" target="_blank">React Server Components</a> and <a href="https://react.dev/reference/rsc/server-actions" target="_blank">Server Actions</a>, and as a result the official React documentation even started <a href="https://react.dev/learn/start-a-new-react-project#production-grade-react-frameworks" target="_blank">recommending Next.js for new projects</a>.</p>



<h3 id="h-key-features-of-next-js" class="wp-block-heading">Key Features of Next.js</h3>



<p class="wp-block-paragraph">Next.js offers features that make it stand out from other web development frameworks.</p>



<h4 id="h-routing" class="wp-block-heading">Routing</h4>



<p class="wp-block-paragraph">Most web development frameworks offer some sort of routing system, which enables you to connect a URL route (e.g. &#8220;/employees/123/edit&#8221;) to the piece of code responsible for responding to requests for that URL. Next.js offers a particularly effective routing system. It&#8217;s <em>file-based</em>, which means that requests to your web application will be routed a given React component based on the names of its folders and files.</p>



<p class="wp-block-paragraph">For example, in the file structure depicted in the below screenshot, a request to &#8220;/admin/addCompetency&#8221; will be handled by the file &#8220;app/admin/addCompetency/page.tsx&#8221;. This is intuitive <em>and</em> it enforces a disciplined approach to organizing your code files. If a new developer on the team is assigned a task to troubleshoot that &#8220;/admin/addCompetency&#8221; page, they know exactly where to start.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/routing-screenshot.png" alt="Example of folder-based routing in Next.js"/></figure>



<p class="wp-block-paragraph">But that&#8217;s only a small part of what Next.js&#8217; routing system offers us. In the above screenshot, you can see an &#8220;error.tsx&#8221; file. By including this file in this folder, we automatically wrap our page in an <a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary" target="_blank">error boundary</a>. Any uncaught exceptions/errors on the &#8220;/admin/addCompetency&#8221; page will be handling by the component defined in &#8220;error.tsx&#8221; and a fallback UI will be displayed.</p>



<p class="wp-block-paragraph">Additionally, we can put a &#8220;loading.tsx&#8221; file in that folder, and it will cause a fallback UI (loading indicator) to be displayed while we wait for that portion of the page to finish loading. This feature allows us to implement <a href="https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming" target="_blank">loading UI and streaming</a> with almost no effort.</p>



<p class="wp-block-paragraph">Next.js&#8217; routing system also offers dynamic routes, parallel routes, intercepting routes, and other features that make it easy to compose a complex UI while keeping our codebase simple and well-organized. <a href="https://nextjs.org/docs/app/building-your-application/routing" target="_blank">Read more about it routing in Next.js.</a></p>



<h4 id="h-server-components" class="wp-block-heading">Server Components</h4>



<p class="wp-block-paragraph">I&#8217;ve been using React for web application development since 2016. And when I first started, I distinctly remember thinking, &#8220;Okay, but how do we get data from the server into our React components?&#8221; The official answer from React back then was <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f937-200d-2642-fe0f.png" alt="🤷‍♂️" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">In 2016, the common way to load data into a React component (without third-party libraries for data fetching) was to do a fetch call from within your component&#8217;s &#8220;componentDidMount&#8221; lifecycle method. Honestly, it was kind of gross. With React 16.8 things got a little better with the useEffect hook. And of course, today, in a React SPA the best way to load data from the server into a component is with a third-party library like Tanstack Query (react-query).</p>



<p class="wp-block-paragraph">But React has never offered a first-class method of getting data from the server and giving it to our React components. Until now. React has introduced the concept of React Server Components (RSC), and Next.js is the React framework to make this feature available. With RSCs, we can write components that run on the server. The components can be defined as async functions, and therefore we can <em>await</em> asynchronous operations in them.</p>



<p class="wp-block-paragraph">For example, here&#8217;s a server component for a select list of &#8220;categories&#8221;. I&#8217;ve defined this as an async function, so it can only run on the server, but this allows us to get data directly from our database (which is what db_getCategories() does in this example). Because we await the result, we can guarantee that by the time we reach the next line of our code, we have the data.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import db_getCategories from "@/database/queries/db_getCategories";
import linq from "linq";

export async function CategorySelect() {
  const categories = await db_getCategories();
  const orderedCategories = linq
    .from(categories)
    .orderBy((c) => c.Name)
    .toArray();
  return (
    &lt;select name="category_id">
      {orderedCategories.map((category) => (&lt;option key="{category.Id}" value="{category.Id}">
          {category.Name}&lt;/option>
      ))}
    &lt;/select>
  );
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">db_getCategories</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;@/database/queries/db_getCategories&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">linq</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;linq&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">CategorySelect</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">categories</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">db_getCategories</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">orderedCategories</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">linq</span></span>
<span class="line"><span style="color: #D4D4D4">    .</span><span style="color: #DCDCAA">from</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">categories</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    .</span><span style="color: #DCDCAA">orderBy</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">Name</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    .</span><span style="color: #DCDCAA">toArray</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">    &lt;</span><span style="color: #9CDCFE">select</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;category_id&quot;</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      {</span><span style="color: #9CDCFE">orderedCategories</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">map</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">category</span><span style="color: #D4D4D4">) =&gt; (&lt;</span><span style="color: #4EC9B0">option</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">key</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{category.Id}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">value</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{category.Id}&quot;</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">          {category.Name}&lt;/</span><span style="color: #4EC9B0">option</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      ))}</span></span>
<span class="line"><span style="color: #D4D4D4">    &lt;/</span><span style="color: #9CDCFE">select</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This results in a very simple component: first we get the data, then we render the data as JSX. No state, no effects, no memoization &#8211; just simple, straightforward logic that&#8217;s easy for a developer to understand.</p>



<p class="wp-block-paragraph">But what sets React Server Components apart from other server-side rendering solutions is that they can be used to <em>stream</em> parts of the UI to the web browser. So as opposed to the good old days when we used to render the HTML for an entire web page on the server before sending it to the web browser, we can leverage server components along with React Suspense to stream our UI in chunks to the user complete with loading indicators:</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/react-suspense-code-sample.png" alt="Code sample of wrapping a server component with React Suspense"/></figure>



<p class="wp-block-paragraph">Additionally, because the output of these server components is a minimal data format called the RSC Payload, rather than rendered HTML, server components do not have a significant impact on a web application&#8217;s I/O throughput and therefore should not have a negative impact on scalability in most cases.</p>



<p class="wp-block-paragraph">This is incredibly powerful. And to someone who&#8217;s been developing React applications for years, this feels like someone&#8217;s finally given me the keys to a car that I&#8217;ve just been hotwiring until now.</p>



<h4 id="h-server-actions" class="wp-block-heading">Server Actions</h4>



<p class="wp-block-paragraph">Server Actions are similar to Server Components in that they&#8217;re a React feature that Next.js has been the first framework to incorporate. Whereas React Server Components are designed to make loading/fetching data easier, Server Actions are designed for <em>mutating</em> data.</p>



<p class="wp-block-paragraph">Server Actions are defined similar to any other JavaScript function, except with a &#8220;use server&#8221; directive added in to signal that they&#8217;re Server Actions. Then Next.js takes these functions and essentially turns them into HTTP endpoints for us, and by calling these functions from a client component we automatically get an HTTP request sent to that endpoint.</p>



<p class="wp-block-paragraph">Here&#8217;s an example Server Action, that takes some simple parameters and makes a corresponding update in the database (db_updateCompetencyForEmployee()) based on those parameters and the authentication information from the request (await getUserSession()).</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>"use server";

export async function updateCompetencyForEmployee(
  competencyId: number,
  proficiencyId: number,
  learningPreference: boolean,
  notes: string | null
) {
  const userSession = await getUserSession();
  if (!userSession) {
    return { success: false, error: "User not logged in" };
  }
  const onprem_sid = userSession.user.onprem_sid;
  if (!onprem_sid) {
    return {
      success: false,
      error: "User session does not contain onprem_sid",
    };
  }
  const employee = await db_getEmployeeByOnpremSid(onprem_sid);
  if (isNaN(competencyId) || isNaN(proficiencyId) || !employee) {
    return {
      success: false,
      error: "Invalid competencyId, proficiencyId, or employeeId",
    };
  }
  await db_updateCompetencyForEmployee(
    employee.Id,
    competencyId,
    proficiencyId,
    learningPreference,
    notes
  );
  return { success: true };
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #CE9178">&quot;use server&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">updateCompetencyForEmployee</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">competencyId</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">number</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">proficiencyId</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">number</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">learningPreference</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">boolean</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">notes</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4"> | </span><span style="color: #4EC9B0">null</span></span>
<span class="line"><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">userSession</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">getUserSession</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">userSession</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">success:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">error:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;User not logged in&quot;</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">onprem_sid</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">userSession</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">user</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">onprem_sid</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">onprem_sid</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">success:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">error:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;User session does not contain onprem_sid&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    };</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">employee</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">db_getEmployeeByOnpremSid</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">onprem_sid</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #DCDCAA">isNaN</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">competencyId</span><span style="color: #D4D4D4">) || </span><span style="color: #DCDCAA">isNaN</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">proficiencyId</span><span style="color: #D4D4D4">) || !</span><span style="color: #9CDCFE">employee</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">success:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">error:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;Invalid competencyId, proficiencyId, or employeeId&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    };</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">db_updateCompetencyForEmployee</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">employee</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">Id</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">competencyId</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">proficiencyId</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">learningPreference</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">notes</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">success:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Then from a client component, we can call this Server Action function directly, and under the hood React/Next.js will make an HTTP request to the server with those parameter values in the body of the request.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>&lt;label htmlfor="{`${fieldPrefix}-proficiency-${competencyId}`}">
  &lt;input onclick="{async" type="radio" /> {
      const response = await updateCompetencyForEmployee(
        competencyId,
        proficiencyId,
        props.learningPreference,
        props.notes
      );
      if (!response.success) {
        alert(`ERROR: ${response.error}`);
      } 
    }}
  />
  {proficiencyName}
&lt;/label></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">&lt;</span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">htmlfor</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{`${fieldPrefix}-proficiency-${competencyId}`}&quot;</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  &lt;</span><span style="color: #9CDCFE">input</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{async&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;radio&quot;</span><span style="color: #D4D4D4"> /&gt; {</span></span>
<span class="line"><span style="color: #D4D4D4">      const </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">updateCompetencyForEmployee</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">competencyId</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">proficiencyId</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">learningPreference</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">notes</span></span>
<span class="line"><span style="color: #D4D4D4">      );</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">if</span><span style="color: #D4D4D4"> (!response.success) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">alert</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">`ERROR: </span><span style="color: #569CD6">${</span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">error</span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">      } </span></span>
<span class="line"><span style="color: #D4D4D4">    }}</span></span>
<span class="line"><span style="color: #D4D4D4">  /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  {</span><span style="color: #9CDCFE">proficiencyName</span><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">&lt;/</span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">One way to think of Server Actions is that they&#8217;ve made it far easier to cross the barrier between the client and the server.</p>



<p class="wp-block-paragraph">A really nifty way to use Server Actions is to provide them to the &#8220;action&#8221; attribute of a&nbsp; element like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>export function AddCategoryForm() {
  return (
    &lt;form action={addCompetencyCategory}>
      &lt;FormLabel htmlFor="add_category">
        Add Category
        &lt;input
          type="text"
          name="add_category"
          placeholder="New Category Name"
        />
      &lt;/FormLabel>
      &lt;FormSubmitButton pendingText="Saving..." type="submit">
        Add Category
      &lt;/FormSubmitButton>
      &lt;p>
        &lt;FormStatusMessageFromUrl />
      &lt;/p>
    &lt;/form>
  );
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">AddCategoryForm</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">    &lt;</span><span style="color: #9CDCFE">form</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">={</span><span style="color: #9CDCFE">addCompetencyCategory</span><span style="color: #D4D4D4">}&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;</span><span style="color: #9CDCFE">FormLabel</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">htmlFor</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;add_category&quot;</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">Add</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">Category</span></span>
<span class="line"><span style="color: #D4D4D4">        &lt;</span><span style="color: #9CDCFE">input</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;text&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;add_category&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #9CDCFE">placeholder</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;New Category Name&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">        /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;/</span><span style="color: #9CDCFE">FormLabel</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;</span><span style="color: #9CDCFE">FormSubmitButton</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">pendingText</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Saving...&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;submit&quot;</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">Add</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">Category</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;/</span><span style="color: #9CDCFE">FormSubmitButton</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;</span><span style="color: #4EC9B0">p</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        &lt;</span><span style="color: #9CDCFE">FormStatusMessageFromUrl</span><span style="color: #D4D4D4"> /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      &lt;/</span><span style="color: #9CDCFE">p</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    &lt;/</span><span style="color: #9CDCFE">form</span><span style="color: #D4D4D4">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<pre class="wp-block-code"><code>
export function AddCategoryForm() {
  return (
    </code></pre>


<form action="{addCompetencyCategory}">Add Category <input name="add_category" type="text" placeholder="New Category Name" /> Add Category</form>


<p class="wp-block-paragraph"><formstatusmessagefromurl> </formstatusmessagefromurl></p>



<p class="wp-block-paragraph">); }</p>



<p class="wp-block-paragraph">With this simple form, we don&#8217;t need to manage state at all! This is very similar to plain HTML form handling, where pressing the submit button triggers the values of the form&#8217;s fields to be bundled up and sent to the provided URL as an HTTP request.</p>



<p class="wp-block-paragraph">Here&#8217;s the server action being used in the above example:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>"use server";
// ...imports...
export async function addCompetencyCategory(formData: FormData) {
  // authorization
  if (!(await getUserIsAdmin())) {
    redirect(
      "/admin?error=" +
        encodeURIComponent("You must be an admin to add a category")
    );
  }
  // form data validation with ZOD
  const { data, success, error } = await schema.safeParseAsync(formData);
  if (!success) {
    const errorMessages = error.issues.map((i) => i.message).join("\n");
    // redirect with error message if failed validation
    redirect("/admin?error=" + encodeURIComponent(errorMessages));
  }
  const { add_category: name } = data;
  // save to database
  await db_addCompetencyCategory(name);
  revalidatePath("/admin");
  // redirect to "success" page
  redirect(
    "/admin?message=" + encodeURIComponent("Category added successfully")
  );
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #CE9178">&quot;use server&quot;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #6A9955">// ...imports...</span></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">addCompetencyCategory</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">formData</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">FormData</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// authorization</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!(</span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">getUserIsAdmin</span><span style="color: #D4D4D4">())) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">redirect</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #CE9178">&quot;/admin?error=&quot;</span><span style="color: #D4D4D4"> +</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">encodeURIComponent</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;You must be an admin to add a category&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// form data validation with ZOD</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">success</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">error</span><span style="color: #D4D4D4"> } = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">schema</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">safeParseAsync</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">formData</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">success</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">errorMessages</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">issues</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">i</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">i</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">message</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">join</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;</span><span style="color: #D7BA7D">\n</span><span style="color: #CE9178">&quot;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// redirect with error message if failed validation</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">redirect</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;/admin?error=&quot;</span><span style="color: #D4D4D4"> + </span><span style="color: #DCDCAA">encodeURIComponent</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">errorMessages</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">add_category</span><span style="color: #D4D4D4">: </span><span style="color: #4FC1FF">name</span><span style="color: #D4D4D4"> } = </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// save to database</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">db_addCompetencyCategory</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">revalidatePath</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;/admin&quot;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// redirect to &quot;success&quot; page</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">redirect</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #CE9178">&quot;/admin?message=&quot;</span><span style="color: #D4D4D4"> + </span><span style="color: #DCDCAA">encodeURIComponent</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;Category added successfully&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">An interesting benefit of this approach is the form submission is fully functional <em>even if the user has JavaScript disabled</em>. We don&#8217;t need that for most projects, but for those few projects where we need to the application to still be functional with JavaScript disabled, this is an enormous benefit. If the user does have JavaScript enabled, though, Next.js automatically transforms the behavior of this simple form to act like a form in a modern SPA, because once the React component tree hydrates the client-side behavior gets enabled. This is called <a href="https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement" target="_blank">progressive enhancement</a>.</p>



<h4 id="h-api-routes" class="wp-block-heading">API routes</h4>



<p class="wp-block-paragraph">In addition to its full-stack capabilities, Next.js allows us to define more traditional endpoints via &#8220;route handlers&#8221;. These use the same kind of file-based routing as the rest of the framework, but route handlers allow us to return any kind of HTTP response we want in response to a request. For example, this GET route handler at &#8220;/api/projects/{projectId}/expenseReceipts/projectFiles&#8221; returns a file that can be downloaded by the browser:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>export async function GET(
  request: Request,
  { params }: { params: { projectId: string } }
) {
  if (!getUserIsLoggedIn()) {
    return new Response("Unauthorized", { status: 401 });
  }
  const acumaticaApiEndpoint = `${process.env.ACUMATICA_API_URL}/ExpenseReceipt/ProjectFiles?projectId=${params.projectId}`;
  const acumaticaResponse = await fetch(acumaticaApiEndpoint);
  if (acumaticaResponse.ok) {
    const bodyAsBuffer = await acumaticaResponse.arrayBuffer();
    return new Response(bodyAsBuffer, {
      headers: {
        "Content-Type": "application/octet-stream",
        "Content-Disposition": `attachment; filename="projectFiles.zip"`,
      },
    });
  }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">GET</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">request</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Request</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">  { </span><span style="color: #9CDCFE">params</span><span style="color: #D4D4D4"> }: { </span><span style="color: #9CDCFE">params</span><span style="color: #D4D4D4">: { </span><span style="color: #9CDCFE">projectId</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4"> } }</span></span>
<span class="line"><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #DCDCAA">getUserIsLoggedIn</span><span style="color: #D4D4D4">()) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Response</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;Unauthorized&quot;</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">status:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">401</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">acumaticaApiEndpoint</span><span style="color: #D4D4D4"> = </span><span style="color: #CE9178">`</span><span style="color: #569CD6">${</span><span style="color: #9CDCFE">process</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">env</span><span style="color: #D4D4D4">.</span><span style="color: #4FC1FF">ACUMATICA_API_URL</span><span style="color: #569CD6">}</span><span style="color: #CE9178">/ExpenseReceipt/ProjectFiles?projectId=</span><span style="color: #569CD6">${</span><span style="color: #9CDCFE">params</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">projectId</span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">acumaticaResponse</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">acumaticaApiEndpoint</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">acumaticaResponse</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">ok</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">bodyAsBuffer</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">acumaticaResponse</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">arrayBuffer</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Response</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">bodyAsBuffer</span><span style="color: #D4D4D4">, {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">headers:</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&quot;Content-Type&quot;</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;application/octet-stream&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&quot;Content-Disposition&quot;</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">`attachment; filename=&quot;projectFiles.zip&quot;`</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      },</span></span>
<span class="line"><span style="color: #D4D4D4">    });</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">An advantage of this is that if we have a separate mobile or desktop application that needs to consume endpoints like these, we don&#8217;t need a separate REST API to serve those applications. It can make sense to have a separate REST API anyway, but it&#8217;s not strictly necessary.</p>



<h2 id="h-performance-improvements" class="wp-block-heading"><a name="performance-improvements">Performance Improvements</a></h2>



<h3 id="h-faster-load-times" class="wp-block-heading">Faster Load Times</h3>



<p class="wp-block-paragraph">Thanks to server-side rendering (SSR) with React Server Components, initial load times in a Next.js application can be much faster than the initial page load time of a React SPA. Whereas with an SPA we have to wait for the JavaScript bundle (which is usually hundreds of KB and can sometimes be multiple MB) to download before the user sees any UI, in a Next.js app the initial request for the web page gets fully rendered HTML as a response. Taking a current project as an example, loading the home page is only a 7.4 KB download versus 200 KB if I were developing it as an SPA. This tends to result in a much better score for <a href="https://web.dev/articles/fcp" target="_blank">First Contentful Paint (FCP)</a>, an important Core Web Vitals performance metric.</p>



<p class="wp-block-paragraph">And depending on how much content you&#8217;re able to render on the server for your application, you can even end up seeing the remarkable result of FCP and LCP (Largest Contentful Paint) being the same number! When this happens, it means the very instant that the user first sees content/UI for the web page, they see the entire UI.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/core-web-vitals-sample.png" alt="Core web vitals sample for a real Next.js application"/></figure>



<h3 id="h-caching" class="wp-block-heading">Caching</h3>



<p class="wp-block-paragraph">One of the reasons it&#8217;s easy to develop a highly-performant application with Next.js is because it offers robust caching options. Next.js with app router has four different caching mechanisms, all of which are enabled by default (Next.js 15 will make some of these opt-in).</p>



<ul class="wp-block-list">
<li><strong>Request Memoization</strong> &#8211; If you make multiple fetch requests to the same URL in multiple places in a React component tree, Next.js will only make the request once</li>



<li><strong>Data Cache</strong> &#8211; Same as Request Memoization, but is persistent across multiple requests to our Next.js application, e.g. multiple requests for the same web page</li>



<li><strong>Full Route Cache</strong> &#8211; If a given server component or route handler does not use headers or cookies to determine its output, then the HTML output and/or React Server Component Payload (or HTTP response in the case of route handlers) will be cached on the server as static content until something happens to &#8220;revalidate&#8221; it.</li>



<li><strong>Router Cache</strong> &#8211; This is the client-side caching mechanism in Next.js. It&#8217;s time-based, and by default, if a user has visited a given route in the last 30 seconds, navigating back to that route again within 30 seconds will serve up the same React component tree from the last visit and won&#8217;t make a request to the server at all.</li>
</ul>



<h3 id="h-automatic-code-splitting" class="wp-block-heading">Automatic Code Splitting</h3>



<p class="wp-block-paragraph">Code splitting involves splitting up your JavaScript bundle so that the user&#8217;s browser only downloads enough JavaScript to render the currently viewed web page. In other words, if there&#8217;s only one page in our app that utilizes the react-query package, we can use code splitting to ensure that the web browser doesn&#8217;t need to download react-query&#8217;s bundled JavaScript until they visit that specific page &#8211; visiting other pages will not trigger that chunk of JavaScript to be downloaded.</p>



<p class="wp-block-paragraph">This is something we&#8217;ve been able to do in React for a very long time now, but it&#8217;s fairly involved and is easy to get it wrong. So the fact that Next.js does this automatically for us, and does a good job with it, makes it that much easier to develop an app that delivers as fast and performant an experience as possible.</p>



<h3 id="h-improved-seo" class="wp-block-heading">Improved SEO</h3>



<p class="wp-block-paragraph">Although many of the web application projects we work are not concerned with SEO, some are. For those web applications where good SEO (Search Engine Optimization) is required or at least desired, Next.js helps us achieve better SEO through the following mechanisms:</p>



<ul class="wp-block-list">
<li>Search engine crawlers are better able to fully process the contents of a web page that was rendered on the server than those that are rendered on the client with JavaScript. Next.js enables us to render all our content on the server, and this in turn boosts SEO.</li>



<li>Performance is a factor in SEO. The faster a web page loads, the higher search engine ranking it will get. Next.js&#8217; many mechanisms for improving performance also result in better SEO.</li>



<li>Because most of your links will be rendered on the server with server components, search engine crawlers will have an easier time indexing all the pages on your web site or web application.</li>
</ul>



<p class="wp-block-paragraph">A takeaway here is that, if SEO is important for your web application or website, Next.js is one of your best framework options.</p>



<h2 id="h-developer-experience" class="wp-block-heading"><a name="developer-experience">Developer Experience</a></h2>



<p class="wp-block-paragraph">Developer experience is a broad term that covers workplace culture, management process, and workflow, but for the sake of this article we&#8217;re going to focus on one aspect of developer experience: tools.</p>



<p class="wp-block-paragraph">If you&#8217;re a contractor who builds houses, but you restrict your team from using power tools, your employees are not going to enjoy working for you but equally important is the fact that it will take you more hours and money to complete a project. Similarly, the tools a software development team uses can have an enormous impact on their productivity. A toolset that&#8217;s easy to work with and fits the project&#8217;s specific needs&nbsp;can result in us shipping more features for our client&#8217;s web application for less money, which means we deliver more value to our clients.</p>



<p class="wp-block-paragraph">So let&#8217;s look at some of the ways that Next.js improves the developer experience.</p>



<h3 id="h-simpler-code" class="wp-block-heading">Simpler code</h3>



<p class="wp-block-paragraph">Two of Next.js&#8217; big features &#8211; Server Actions and React Server Components &#8211; streamline the codebase of a React app in a very big way. I did a demo a while back where I developed the same form two ways: with a plain React component using hooks for state management, and with server components and server actions. The difference was bigger than I&#8217;d expected &#8211; the traditional React component was 125 lines of code whereas the Next.js version was only 29 lines of code. In the screenshot below, you can see the &#8220;plain React&#8221; version on the left and the Next.js version on the right.</p>



<p class="wp-block-paragraph">Comparison between a plain React component and the same component written in Next.js</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/react-form-comparison.png" alt="Comparison between a plain React component and the same component written in Next.js"/></figure>



<p class="wp-block-paragraph">It should be noted that neither version of this component utilizes any third party libraries &#8211; only built-in React features. The version on the right simply uses a handful of custom, reusable components, each only a few lines of very simple code. Both versions of the form component have the same behavior: a loading indicator for the select list of categories, temporarily disabling the submit button while the form submission is pending, and a display for error messages a &#8220;form saved successfully&#8221; status message.</p>



<p class="wp-block-paragraph">Not only does the Next.js version of this form component take much less effort to develop, but the code is also much more straightforward &#8211; it&#8217;s purely declarative and contains no imperative logic whatsoever. That almost completely eliminates the potential for buggy behavior (at least on the client-side), but it also makes it very easy to modify and maintain this component in the future. A developer can jump into this code file and understand everything about it with a quick glance.</p>



<p class="wp-block-paragraph">This has the potential to greatly reduce the amount of effort, and therefore money, required to build a web application. The key word there is potential &#8211; an app that requires a large amount of very intricate client-side interactivity may not see as much benefit in terms of the simplicity of the code base and the effort required to develop the app.</p>



<h3 id="h-enhanced-developer-tools" class="wp-block-heading">Enhanced Developer Tools</h3>



<p class="wp-block-paragraph">Hot reloading has become a staple of front end development. When I save a change to a file in my application, I want to see that change reflected in my web browser without needing to restart the application or even reload my browser. Next.js provides a particularly smooth hot reloading experience. If I save a change to a server component, hot reload will cause that server component to automatically be requested and re-rendered again, updating the UI instantaneously for all intents and purposes. The same is true with client-side components. This full-stack hot reload makes testing changes during development a breeze.</p>



<p class="wp-block-paragraph">Also, I&#8217;ve found that generative AI tools like GitHub Copilot and ChatGPT are particularly effective at generating code for server components and server actions. Similarly, the server side of a Next.js app will use Node.js APIs and libraries, which generative AI also has an easy time with. So tools like GitHub Copilot can enhance a Next.js developer&#8217;s productivity even further, which again, helps us deliver more value to our clients when working on a web application.</p>



<h3 id="h-rich-ecosystem-and-community-support" class="wp-block-heading">Rich Ecosystem and Community Support</h3>



<p class="wp-block-paragraph">The old adage about popularity contests being bad <em>does not</em> apply to software development. The more popular a programming language or framework is:</p>



<ul class="wp-block-list">
<li>The easier it is to recruit new developers that are already familiar with it.</li>



<li>The easier it is to find libraries or packages for that framework/library.</li>



<li>The easier it is to find answers to problems or questions via a Google search or a StackOverflow question.</li>
</ul>



<p class="wp-block-paragraph">Next.js with the app router is picking up in terms of popularity, but it also benefits from the fact that Node.js and React are both incredibly popular. In the <a href="https://survey.stackoverflow.co/2023/#section-most-popular-technologies-web-frameworks-and-technologies">2023 StackOverflow Developer Survey</a>, the two most popular web technologies <em>by a mile</em> were Node.js and React:</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/stackoverflow-survey-results.png" alt="StackOverflow developer survey results showing both Node.js and React as the most popular technologies, with more than 40% of respondents using them"/></figure>



<p class="wp-block-paragraph">The takeaway here is that it is very easy to find support, recruits, and libraries when working with Next.js, more than with any other full-stack web framework. One caveat is that there are a lot of React component libraries/packages out there that have not been updated to work in React Server Components, and may never receive such an update. However, any existing third-party React component will work just fine if used from a client component. Some components will need to be imported in a specific way, but as an extreme example I was able to use a very client-heavy React component from an NPM package that hasn&#8217;t been updated since 2019 and it worked just fine. There is some misinformation out there around this point. The most you&#8217;ll have to do to make this work is to do something like the following when important a component:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">TypeScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const ColorThemePicker = dynamic(() => import("./colorThemePicker"), {
  ssr: false,
});</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">ColorThemePicker</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">dynamic</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">import</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;./colorThemePicker&quot;</span><span style="color: #D4D4D4">), {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">ssr:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">});</span></span></code></pre></div>



<h2 id="h-case-studies-and-success-stories" class="wp-block-heading"><a name="case-studies-and-success-stories">Case Studies and Success Stories</a></h2>



<h3 id="h-industry-examples" class="wp-block-heading">Industry Examples</h3>



<p class="wp-block-paragraph">Working with Next.js, we&#8217;re in good company. The following is a small sample of businesses and organizations that have found success with Next.js.</p>



<ul class="wp-block-list">
<li><a href="https://vercel.com/blog/frameio-never-drop-the-illusion" target="_blank">Frame.io (an Adobe company)</a></li>



<li><a href="https://vercel.com/blog/washington-post-next.js-vercel-engineering-at-the-speed-of-breaking-news?nxtPslug=washington-post-next.js-vercel-engineering-at-the-speed-of-breaking-news&amp;utm_source=next-site&amp;utm_medium=wapo_callout&amp;utm_campaign=showcase-new" target="_blank">Washington Post</a></li>



<li><a href="https://vercel.com/blog/runway-enables-next-generation-content-creation-with-ai-and-vercel?utm_source=next-site&amp;utm_medium=runway_callout&amp;utm_campaign=showcase-new" target="_blank">runway</a></li>



<li><a href="https://vercel.com/blog/a-better-developer-experience-makes-building-cruise-critic-more-efficient" target="_blank">Cruise Critic (a TripAdvisor subsidiary)</a></li>



<li><a href="https://nextjs.org/showcase" target="_blank">Others</a> including Nike, ChatGPT, Spotify, Netflix, and Hulu.</li>
</ul>



<h3 id="h-internal-benefits" class="wp-block-heading">Internal Benefits</h3>



<p class="wp-block-paragraph">DMC has been piloting Next.js for a year now, it&#8217;s helped ensure success on several projects:</p>



<ul class="wp-block-list">
<li>DMC has already leveraged Next.js (with App Router) to help a client with a rewrite of their order management web portal. Next.js&#8217; streamlined developer experience and robust features allowed us to complete this project well under budget and deliver a web application that our client was happy with.</li>



<li>We wrote a new internal application (budget tracking app) using Next.js, again with the App Router.</li>



<li>We rewrote several internal web applications using Next.js, and the result for each application was a more performant application with a smoother UX and a simpler codebase.</li>
</ul>



<h2 id="h-future-outlook" class="wp-block-heading"><a name="future-outlook">Future Outlook</a></h2>



<h3 id="h-upcoming-features-and-roadmap" class="wp-block-heading">Upcoming Features and Roadmap</h3>



<p class="wp-block-paragraph">Next.js represents the cutting edge of web application development technology. Moving forward, they plan to improve integration for React 19 features, as well as ship support for the React Compiler, further improving the productivity of our web development team and allow us to achieve more functionality with less code. Next.js is also continually improving its caching features and is implementing <a href="https://nextjs.org/blog/next-14#partial-prerendering-preview" target="_blank">partial prerendering</a>, so this framework aims to become ever more performant as time goes on.</p>



<p class="wp-block-paragraph">DMC looks forward to leveraging Next.js and the App Router to deliver more value to our clients by efficiently building better web applications.</p>



<h3 id="h-ongoing-learning-and-adaptation" class="wp-block-heading">Ongoing Learning and Adaptation</h3>



<p class="wp-block-paragraph">As with any other technology, Next.js has a learning curve. Next.js is an opinionated framework, and our senior application developers have invested time in learning how to work <em>with</em> those opinions rather than against them. Our next steps are to disseminate that knowledge throughout our team so that we can apply best practices to the web application projects we take on for our clients.</p>



<p class="wp-block-paragraph">Aside from internally developed training content, the best learning resources for the Next.js App Router are:</p>



<ul class="wp-block-list">
<li><a href="https://nextjs.org/learn" target="_blank">The official Next.js tutorial</a></li>



<li><a href="https://nextjs.org/docs" target="_blank">The Next.js documentation</a></li>
</ul>



<h2 id="h-conclusion" class="wp-block-heading"><a name="conclusion">Conclusion</a></h2>



<h3 id="h-summary-of-key-points" class="wp-block-heading">Summary of Key Points</h3>



<p class="wp-block-paragraph">To recap, Next.js is an excellent tool for us to utilize on web application projects. Aside from objective technical advantages like performance and SEO, Next.js&#8217; core features allow us to develop a given web application with simpler code and&nbsp;less code, and will often produce a codebase that is easier to maintain over time. The end result of all of this is that our clients end up getting more bang for their buck on those projects that are a good fit for Next.js.</p>



<p class="wp-block-paragraph">To learn more about the benefits of using Next.js for web development, check out the <a href="https://nextjs.org/" target="_blank">Next.js website</a>, and to learn how to use Next.js check out <a href="https://nextjs.org/docs" target="_blank">the official documentation</a>.</p>



<p class="wp-block-paragraph">If you&#8217;re looking to develop a new application or rewrite an existing web application, DMC&#8217;s expert web developers can help you get up and running fast with a high-quality web application that serves your business needs.</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Why Next.js Is Earning a Place in DMC’s Development Toolkit</strong>.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in using Next.js to create faster, scalable, and maintainable <a href="https://static.dmcinfo.com/services/application-development/web-application-development/" data-type="page" data-id="692">web applications</a> with a modern developer experience.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/16026/why-dmc-is-adopting-next-js-for-web-application-development/">Why DMC is Adopting Next.js for Web Application Development</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>React Smart Components: Lessons Learned While Upgrading an Old Redux Application</title>
		<link>https://static.dmcinfo.com/blog/16056/react-smart-components-lessons-learned-while-upgrading-an-old-redux-application/</link>
		
		<dc:creator><![CDATA[Christopher Olsen]]></dc:creator>
		<pubDate>Thu, 15 Aug 2024 11:38:25 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/16056/react-smart-components-lessons-learned-while-upgrading-an-old-redux-application/</guid>

					<description><![CDATA[<p>Recently, we decided to modernize a legacy web application. This application was written back in 2018, before hooks were introduced to React. We used Redux for state management. As the years went on, and more features were added to React, Redux started to feel like an unnecessary complexity for this application. So, the first question [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16056/react-smart-components-lessons-learned-while-upgrading-an-old-redux-application/">React Smart Components: Lessons Learned While Upgrading an Old Redux Application</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Recently, we decided to modernize a legacy <a href="https://static.dmcinfo.com/services/application-development/web-application-development">web application</a>. This application was written back in 2018, before hooks were introduced to React. We used Redux for state management. As the years went on, and more features were added to React, Redux started to feel like an unnecessary complexity for this application.</p>



<p class="wp-block-paragraph">So, the first question to answer was this: should we update this application to use the latest version of Redux (preferably with the <a href="https://redux-toolkit.js.org/" target="_blank">Redux Toolkit</a>), or should we completely replace our Redux store—along with all the accompanying actions, reducers, and sagas—with simpler, lightweight state management in the form of hooks and libraries like <a href="https://tanstack.com/query/latest/docs/framework/react/overview" target="_blank">React Query</a>?</p>



<p class="wp-block-paragraph">One of my biggest complaints about the way we used Redux in this application is that it requires writing a lot of boilerplate. If&nbsp;we were to stick with Redux, we would want to switch to Redux Toolkit, which allows you to accomplish the same functionality with a fraction of the boilerplate. However, this would involve rewriting all our Redux code. I did a proof of concept with a small portion of our Redux store, and it was clear that updating our Redux codebase would be akin to rewriting all our state management. So, keeping Redux in this application would not take less effort than just replacing Redux entirely for state management.</p>



<p class="wp-block-paragraph">We explored the path of switching from Redux to hooks for our state management, and it turned out to be surprisingly straightforward. In this blog post, we&#8217;ll explore why that is and what implications this has for structuring a React codebase. In the process, we&#8217;ll discuss whether an old concept—smart components vs. dumb components—still has a place in modern React development.</p>



<h2 id="h-sample-of-our-redux" class="wp-block-heading">Sample of Our Redux</h2>



<p class="wp-block-paragraph">To demonstrate what &#8220;flavor&#8221; of Redux our application used, let&#8217;s look at a component named &#8220;NewEntryForm&#8221;:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
interface FormProps {
    isVisible: boolean;
    selectedEmployeeName: string;
    selectedDate: Date | null;
    employees: Employee[];
    selectEmployee: (name: string) => void;
    selectDate: (date: Date | null) => void;
    closeForm: () => void;
    save: () => void;
}

export function NewEntryForm({
    closeForm,
    isVisible,
    save,
    selectedDate,
    selectedEmployeeName,
    selectDate,
    selectEmployee,
    employees
}: FormProps) {
    return (
        &lt;panel islightdismiss="{true}" isopen="{isVisible}" ondismiss="{closeForm}" type="{PanelType.smallFixedFar}">
            &lt;dropdown idropdownoption="" label="Employee" onchanged="{(item:" selectedkey="{selectedEmployeeName}"> selectEmployee(item.key as string)}
                options={employees.map(e => ({ key: e.name, text: e.name } as IDropdownOption))}
            />
            &lt;datepicker firstdayofweek="{DayOfWeek.Sunday}" value="{selectedDate"> selectDate(date || null)}
            />&lt;button onclick="{closeForm}">Cancel&lt;/button>&lt;button onclick="{save}">Save&lt;/button>
        &lt;/datepicker>&lt;/dropdown>&lt;/panel>
    );
}

export default connect(
    (state: FullStore) => ({
        isVisible: newEntryFormIsVisibleSelector(state),
        selectedEmployeeName: newEntryEmployeeNameSelector(state),
        selectedDate: newEntryDateSelector(state),
        employees: employeeOptionsForNewEntryFormSelector(state)
    }),
    (dispatch: Dispatch&lt;appaction>) =>
        bindActionCreators(
            {
                selectEmployee: selectEmployeeForNewEntry,
                selectDate: selectDateForNewEntry,
                closeForm: hideNewEntryForm,
                save: saveNewTimeEntry
            },
            dispatch
        )
)(NewEntryForm);
&lt;/appaction></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #569CD6">interface</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">FormProps</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">isVisible</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">boolean</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Date</span><span style="color: #D4D4D4"> | </span><span style="color: #4EC9B0">null</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">employees</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Employee</span><span style="color: #D4D4D4">[];</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">selectEmployee</span><span style="color: #D4D4D4">: (</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">selectDate</span><span style="color: #D4D4D4">: (</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Date</span><span style="color: #D4D4D4"> | </span><span style="color: #4EC9B0">null</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">closeForm</span><span style="color: #D4D4D4">: () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">save</span><span style="color: #D4D4D4">: () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">NewEntryForm</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">closeForm</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">isVisible</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectDate</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">selectEmployee</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">employees</span></span>
<span class="line"><span style="color: #D4D4D4">}: </span><span style="color: #4EC9B0">FormProps</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">panel</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">islightdismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">isopen</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{isVisible}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ondismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{PanelType.smallFixedFar}&quot;</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">dropdown</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">idropdownoption</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Employee&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onchanged</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{(item:&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectedkey</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedEmployeeName}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> selectEmployee(item.key as string)}</span></span>
<span class="line"><span style="color: #D4D4D4">                options=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">employees</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({ </span><span style="color: #9CDCFE">key:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">text:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">IDropdownOption</span><span style="color: #D4D4D4">))</span><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">datepicker</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">firstdayofweek</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{DayOfWeek.Sunday}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedDate&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> selectDate(date || null)}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span><span style="color: #808080">&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Cancel</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{save}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Save</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">datepicker</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">dropdown</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">panel</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">connect</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    (</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">FullStore</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">isVisible:</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">newEntryFormIsVisibleSelector</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">selectedEmployeeName:</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">newEntryEmployeeNameSelector</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">selectedDate:</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">newEntryDateSelector</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">employees:</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">employeeOptionsForNewEntryFormSelector</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">    }),</span></span>
<span class="line"><span style="color: #D4D4D4">    (</span><span style="color: #9CDCFE">dispatch</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Dispatch</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">appaction</span><span style="color: #D4D4D4">&gt;) </span><span style="color: #569CD6">=&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">bindActionCreators</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">            {</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #9CDCFE">selectEmployee:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectEmployeeForNewEntry</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #9CDCFE">selectDate:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectDateForNewEntry</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #9CDCFE">closeForm:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">hideNewEntryForm</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #9CDCFE">save:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">saveNewTimeEntry</span></span>
<span class="line"><span style="color: #D4D4D4">            },</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">dispatch</span></span>
<span class="line"><span style="color: #D4D4D4">        )</span></span>
<span class="line"><span style="color: #D4D4D4">)(</span><span style="color: #9CDCFE">NewEntryForm</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">&lt;/</span><span style="color: #9CDCFE">appaction</span><span style="color: #D4D4D4">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">Although this code is now seven&nbsp;years old and pre-dates hooks, it should look familiar to anyone who&#8217;s used React with TypeScript. We&#8217;re declaring the type of props, and then we&#8217;re declaring a <em>pure</em> functional component. I call this component pure because it will always render the same output for a given set of props.</p>



<p class="wp-block-paragraph">The part that&#8217;s unique to old-school Redux is the bit involving <code>connect()</code> at the bottom. This is a higher-order component provided by the React-Redux library, and it does exactly what it sounds like—it &#8220;connects&#8221; a React component to the Redux store. It gets various action handlers and pieces of state from the Redux store and provides those things as <em>props</em> to our React component. Here&#8217;s where I get to invoke a term that was popular in the early days of React.</p>



<p class="wp-block-paragraph">In this example, the function &#8220;NewEntryForm&#8221; is what is known as a <em>presentational component</em>. It does nothing more than take props and render JSX based on those props. It doesn&#8217;t manage any of its own state. The component created by the <code>connect</code> function, on the other hand, is what&#8217;s known as a <em>container component</em>. Container and presentational components are also known as <em>smart </em>and <em>dumb</em> components, or <em>stateful </em>and <em>pure</em> components.</p>



<p class="wp-block-paragraph">When I want to use this component, I can choose to use the smart component by doing a default import:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
import NewEntryForm from './newEntryForm'
Or I can choose to use the "dumb" component by doing a regular import:

import {NewEntryForm} from './newEntryForm'
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">NewEntryForm</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;./newEntryForm&apos;</span></span>
<span class="line"><span style="color: #9CDCFE">Or</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">I</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">can</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">choose</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">to</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">use</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">the</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;dumb&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">component</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">by</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">doing</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">a</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">regular</span><span style="color: #D4D4D4"> </span><span style="color: #C8C8C8">import</span><span style="color: #D4D4D4">:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> {</span><span style="color: #9CDCFE">NewEntryForm</span><span style="color: #D4D4D4">} </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;./newEntryForm&apos;</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">One of the advantages of this approach is that it makes the component more reusable. If I want to reuse this same component in two places, but it needs to get its state from somewhere different in both places, I can simply have two different <code>connect()</code> HOCs that get the props from different parts of the Redux store and use them both to render the NewEntryForm component. In other words, two smart components rendering the same <em>dumb</em> component.</p>



<h2 id="h-replacing-redux-with-hooks" class="wp-block-heading">Replacing Redux with Hooks</h2>



<p class="wp-block-paragraph">With few exceptions, every component in the application followed this pattern. We were storing most of our application&#8217;s state in the Redux store, and so all our smart components were just using <code>connect()</code> to grab state and event handlers from the Redux store and passing them to our dumb components as props.</p>



<p class="wp-block-paragraph">This is an extremist approach to Redux development, but it did come with a benefit: we can replace the state management for the application with minimal changes to our component files themselves.</p>



<p class="wp-block-paragraph">Take another look at that NewEntryForm component file. Everything up until the <code>connect()</code> part can stay the same, and I can just replace the <code>connect()</code> HOC with the following:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
interface NewEntryFormContainerProps {
    closeForm: () => void;
    save: (employeeName: string, date: Date) => void;
}

export default function NewEntryFormContainer({ closeForm, save }: NewEntryFormContainerProps) {
    const employeesQuery = useAllEmployeesQuery();
    const employees = useMemo(
        () =>
            employeesQuery.data
                ? linq
                      .from(employeesQuery.data)
                      .where((e) => e.isActive)
                      .orderBy((e) => e.name)
                      .toArray()
                : [],
        &#91;employeesQuery.data&#93;
    );
    const &#91;selectedEmployeeName, setSelectedEmployeeName&#93; = useState&lt;string>();
    const &#91;selectedDate, selectDate&#93; = useState&lt;date>(null);
    const saveCallback = useCallback(() => {
        if (!selectedEmployeeName || !selectedDate) {
            return;
        }
        save(selectedEmployeeName, selectedDate);
    }, &#91;selectedEmployeeName, selectedDate, save&#93;);

    return (
        &lt;newentryform isvisible="{true}" selectedemployeename="{selectedEmployeeName">
    );
}
&lt;/newentryform>&lt;/date>&lt;/string></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #569CD6">interface</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">NewEntryFormContainerProps</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">closeForm</span><span style="color: #D4D4D4">: () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">save</span><span style="color: #D4D4D4">: (</span><span style="color: #9CDCFE">employeeName</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Date</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">NewEntryFormContainer</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">closeForm</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4"> }: </span><span style="color: #4EC9B0">NewEntryFormContainerProps</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">employeesQuery</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useAllEmployeesQuery</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">employees</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        () </span><span style="color: #569CD6">=&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span></span>
<span class="line"><span style="color: #D4D4D4">                ? </span><span style="color: #9CDCFE">linq</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">from</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">where</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">isActive</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">orderBy</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">toArray</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">                : [],</span></span>
<span class="line"><span style="color: #D4D4D4">        &#91;</span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">&#93;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSelectedEmployeeName</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">selectDate</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">date</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">saveCallback</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4"> || !</span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">save</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">newentryform</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">isvisible</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectedemployeename</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedEmployeeName&quot;</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #808080">&lt;/</span><span style="color: #569CD6">newentryform</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">&lt;/</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">&gt;&lt;/</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">This new component still has two props (closeForm and save) because in the new version of the app, we decided to keep that state at a higher level of our component tree, but we&#8217;re getting everything else from some local hooks instead of from Redux. We&#8217;re now getting &#8220;selectedEmployeeName&#8221; and &#8220;setSelectedEmployeeName&#8221; from <code>useState()</code> instead of from a Redux store. We&#8217;re getting our array of employees from <code>useQuery()</code>, and we&#8217;re doing some filtering and sorting within a <code>useMemo()</code> to get the final value that we pass to the &#8220;employees&#8221; prop of the presentational component.</p>



<p class="wp-block-paragraph">To summarize, this component has a very specific job: manage state and provide that state as props to a presentational component.</p>



<p class="wp-block-paragraph">If we want, we can also choose to put this &#8220;smart&#8221; or &#8220;container&#8221; component in a separate file. We could name the file with the presentational component &#8220;newEntryForm.presentation.tsx&#8221; and we could name this new file &#8220;newEntryForm.container.tsx&#8221;.</p>



<p class="wp-block-paragraph">This container vs. presentation paradigm isn&#8217;t specific to migrating from Redux. Migrating from Redux to any other state management solution (Zustand, MobX, perhaps even React Server Components in a framework like Next.js) would be simpler than it would have been if our state and presentation was intermingled.</p>



<h2 id="h-does-this-mean-container-components-are-making-a-comeback" class="wp-block-heading">Does This Mean Container Components are Making a Comeback?</h2>



<h3 id="h-benefits-of-separation-of-concerns" class="wp-block-heading">Benefits of Separation of Concerns</h3>



<p class="wp-block-paragraph">With that in mind, some benefits of having separate container components are:</p>



<ul class="wp-block-list">
<li><strong>Code reuse.</strong> Presentational components are more flexible and easier to reuse, because they maintain no state of their own. In the example above, I&#8217;m getting an array of all active employees for the &#8220;employees&#8221; value. If in a different part of the app I want to reuse this same form but only allow the user to select from their direct reports instead of&nbsp;<em>all</em>&nbsp;employees, I can reuse the &#8220;NewEntryForm&#8221; component as is by&nbsp;wrapping it in a container that gets the &#8220;employees&#8221; array from a query of the user&#8217;s direct reports. Same presentational component, different container.<br>
 &nbsp;</li>



<li><strong>Easier refactoring of state.</strong> In the example above, if I want to switch from using hooks for state management and instead use Zustand for state management, I don&#8217;t need to change anything about my &#8220;NewEntryForm&#8221; component. I only have to modify or rewrite the container. Even for a more drastic change, like migrating the application to Next.js with app router and React Server Components (RSCs), I can migrate my &#8220;NewEntryForm&#8221; component unchanged and only have to replace my container component with a server component. That server component would probably involve using a database request to get the array of employees. We might also replace the &#8220;save&#8221; function with a server action. Finally, we would likely still have a client component for the container, which is where we would set up our &#8220;useState&#8221; and other hooks. In the end, the NewEntryForm component doesn&#8217;t need to be changed at all.</li>
</ul>



<p class="wp-block-paragraph">The separation of concerns principle has other benefits, but some of those benefits may not apply to the presentational vs. container components paradigm. One of the benefits of separation of concerns is maintainability—you can make changes to one layer without changing the other. In the example above, we could switch from, e.g., a Dropdown for the employee select to a ComboBox, and we only need to modify the NewEntryForm (presentational) component to do that. We don&#8217;t need to touch the container component.</p>



<p class="wp-block-paragraph">In a real application, this may not help us as much as we&#8217;d like. If we need to add a new feature to this form, e.g., a new &#8220;Comments&#8221; field, we&#8217;ll need to modify both the presentation component and the container—add the new field to the presentation component&#8217;s JSX and add the new piece of state to the container. There are a lot of feature requests or bug fixes that will require modifying both components, which is why many of the benefits of separation of concerns won&#8217;t necessarily apply to container vs. presentational components.</p>



<h2 id="h-functional-components-already-have-separation-of-concerns-built-in" class="wp-block-heading">Functional Components Already Have Separation of Concerns Built in</h2>



<p class="wp-block-paragraph">One reason why container components aren&#8217;t talked about as much as they used to be is because of the nature of functional components and the ease with which they can be refactored. Let&#8217;s write a very simple React component to demonstrate this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
function CustomersList() {
    const customersQuery = useQuery({
        queryKey: &#91;'customers'&#93;,
        queryFn: async () => {
            const response = await fetch('http://someUrl/')
            return response.json()
        }
    })
    const customers = useMemo(() => customersQuery.data || [], &#91;customersQuery&#93;)

    return ({customers.map(c => {c.name})}) 
}
        </textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">CustomersList</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customersQuery</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useQuery</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">queryKey:</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #CE9178">&apos;customers&apos;</span><span style="color: #D4D4D4">&#93;,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">queryFn</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;http://someUrl&/#39;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    })</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customers</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">customersQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> || [], &#91;</span><span style="color: #9CDCFE">customersQuery</span><span style="color: #D4D4D4">&#93;)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> ({customers.map(</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">})}) </span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">        </span></span></code></pre></div>



<p class="wp-block-paragraph">The above component uses <code>react-query</code>&#8216;s &#8220;useQuery&#8221; hook to fetch data from some URL, and then it renders an unordered list based on the data that comes back.</p>



<p class="wp-block-paragraph">At a glance, we can tell this is not a presentational component, because it manages its own state. It&#8217;s also not a container component,&nbsp;because it renders JSX instead of just passing that state as props to other custom components. So, is it just a big nothing?</p>



<p class="wp-block-paragraph">Another way to look at it is that we have two distinct sections of code here: we have a &#8220;state&#8221; section where we&#8217;re doing our <code>useQuery()</code> and <code>useMemo()</code> stuff, and we have a &#8220;presentation&#8221; section where we&#8217;re rendering our UI with JSX. That structure makes it very easy to refactor and restructure this code. If, a couple of months later in development, we want to reuse the presentation part of this component, we can just highlight the <code>return</code> statement of this component and cut/paste it into a new component. There are even some Visual Studio Code extensions that allow you to refactor a React component like this with the click of a button.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
// here we have our refactored presentation component

function CustomersList (props: {customers: Customer[]}) {
    const customers = props.customers
    return ({customers.map(c =>{c.name})}) 
} // and then we have our original state in a container 
    
function CustomersFromEndpoint () { 
    const customersQuery = useQuery({ queryKey: &#91;'customers'&#93;, queryFn: async () => { const response = await fetch('http://someUrl/')   return response.json() } }) 
    const customers = useMemo( () => customersQuery.data || [], &#91;customersQuery&#93; ) 
    return  
} // and now we can re-use our presentation component with another

// container with different state, this time getting the customers list // from some hook that interacts with IndexedDB 

function CustomersFromIndexedDB () { const customers = useIndexedDBCustomersTable() return  }</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #6A9955">// here we have our refactored presentation component</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">CustomersList</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">: {</span><span style="color: #9CDCFE">customers</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Customer</span><span style="color: #D4D4D4">[]}) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customers</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">customers</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> ({customers.map(</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4">{</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">})}) </span></span>
<span class="line"><span style="color: #D4D4D4">} </span><span style="color: #6A9955">// and then we have our original state in a container </span></span>
<span class="line"><span style="color: #D4D4D4">    </span></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">CustomersFromEndpoint</span><span style="color: #D4D4D4"> () { </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customersQuery</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useQuery</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">queryKey:</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #CE9178">&apos;customers&apos;</span><span style="color: #D4D4D4">&#93;, </span><span style="color: #DCDCAA">queryFn</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;http://someUrl&/#39;</span><span style="color: #D4D4D4">)   </span><span style="color: #9CDCFE">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">() } }) </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customers</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">( () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">customersQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> || [], &#91;</span><span style="color: #9CDCFE">customersQuery</span><span style="color: #D4D4D4">&#93; ) </span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">  </span></span>
<span class="line"><span style="color: #D4D4D4">} </span><span style="color: #6A9955">// and now we can re-use our presentation component with another</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6A9955">// container with different state, this time getting the customers list // from some hook that interacts with IndexedDB </span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">CustomersFromIndexedDB</span><span style="color: #D4D4D4"> () { </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">customers</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useIndexedDBCustomersTable</span><span style="color: #D4D4D4">() </span><span style="color: #9CDCFE">return</span><span style="color: #D4D4D4">  }</span></span></code></pre></div>



<p class="wp-block-paragraph">It could be argued that we no longer need to think ahead and separate our components into presentational and container components because we can separate them whenever we want on a moment&#8217;s notice.</p>



<h2 id="h-it-s-a-spectrum-not-a-binary-choice" class="wp-block-heading">It&#8217;s a Spectrum, not a Binary Choice</h2>



<p class="wp-block-paragraph">Like so many things in life, the statefulness of a React component is not a binary affair; it is a spectrum. Let&#8217;s revisit our NewEntryForm example, but in a new way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
export default function NewEntryFormContainer({ closeForm, save }: NewEntryFormContainerProps) {
    const employeesQuery = useAllEmployeesQuery();
    const employees = useMemo(
        () =>
            employeesQuery.data
                ? linq
                      .from(employeesQuery.data)
                      .where((e) => e.isActive)
                      .orderBy((e) => e.name)
                      .toArray()
                : [],
        &#91;employeesQuery.data&#93;
    );
    const &#91;selectedEmployeeName, setSelectedEmployeeName&#93; = useState&lt;string>();
    const &#91;selectedDate, selectDate&#93; = useState&lt;date>(null);
    const saveCallback = useCallback(() => {
        if (!selectedEmployeeName || !selectedDate) {
            return;
        }
        save(selectedEmployeeName, selectedDate);
    }, &#91;selectedEmployeeName, selectedDate, save&#93;);

    return (
        &lt;panel islightdismiss="{true}" isopen="{true}" ondismiss="{closeForm}" type="{PanelType.smallFixedFar}">
            &lt;dropdown idropdownoption="" label="Employee" onchanged="{(item:" selectedkey="{selectedEmployeeName}"> setSelectedEmployeeName(item.key as string)}
                options={employees.map((e) => ({ key: e.name, text: e.name } as IDropdownOption))}
            />
            &lt;datepicker firstdayofweek="{DayOfWeek.Sunday}" value="{selectedDate"> selectDate(date || null)}
            />&lt;button onclick="{closeForm}">Cancel&lt;/button>&lt;button onclick="{saveCallback}">Save&lt;/button>
        &lt;/datepicker>&lt;/dropdown>&lt;/panel>
    );
}
&lt;/date>&lt;/string></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">NewEntryFormContainer</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">closeForm</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4"> }: </span><span style="color: #4EC9B0">NewEntryFormContainerProps</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">employeesQuery</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useAllEmployeesQuery</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">employees</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        () </span><span style="color: #569CD6">=&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span></span>
<span class="line"><span style="color: #D4D4D4">                ? </span><span style="color: #9CDCFE">linq</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">from</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">where</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">isActive</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">orderBy</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">                      .</span><span style="color: #DCDCAA">toArray</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">                : [],</span></span>
<span class="line"><span style="color: #D4D4D4">        &#91;</span><span style="color: #9CDCFE">employeesQuery</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">&#93;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSelectedEmployeeName</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">selectDate</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">date</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">saveCallback</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4"> || !</span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">save</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">panel</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">islightdismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">isopen</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ondismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{PanelType.smallFixedFar}&quot;</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">dropdown</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">idropdownoption</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Employee&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onchanged</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{(item:&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectedkey</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedEmployeeName}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> setSelectedEmployeeName(item.key as string)}</span></span>
<span class="line"><span style="color: #D4D4D4">                options=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">employees</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({ </span><span style="color: #9CDCFE">key:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">text:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">IDropdownOption</span><span style="color: #D4D4D4">))</span><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">datepicker</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">firstdayofweek</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{DayOfWeek.Sunday}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedDate&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> selectDate(date || null)}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span><span style="color: #808080">&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Cancel</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{saveCallback}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Save</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">datepicker</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">dropdown</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">panel</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">&lt;/</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">&gt;&lt;/</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">This version of the NewEntryForm component is neither purely a container nor purely presentational. It maintains its own state (quite a bit of it, at that), and it returns a very well-defined UI (dropdown, a datepicker, and a couple of buttons). But you should note that this component does not manage <em>all</em> of its own state. It&#8217;s still receiving the &#8220;closeForm&#8221; and &#8220;save&#8221; functions as props rather than defining those functions itself.</p>



<p class="wp-block-paragraph">If we wanted to push this component further toward the &#8220;container&#8221; end of the spectrum, we might decide to define that &#8220;save&#8221; function right here within this component. Instead, let&#8217;s push this component a bit more toward the presentation side of the spectrum to enhance reusability:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
export default function NewEntryFormContainer({ employees, closeForm, save }: NewEntryFormContainerProps) {
    const &#91;selectedEmployeeName, setSelectedEmployeeName&#93; = useState&lt;string>();
    const &#91;selectedDate, selectDate&#93; = useState&lt;date>(null);
    const saveCallback = useCallback(() => {
        if (!selectedEmployeeName || !selectedDate) {
            return;
        }
        save(selectedEmployeeName, selectedDate);
    }, &#91;selectedEmployeeName, selectedDate, save&#93;);

    return (
        &lt;panel islightdismiss="{true}" isopen="{true}" ondismiss="{closeForm}" type="{PanelType.smallFixedFar}">
            &lt;dropdown idropdownoption="" label="Employee" onchanged="{(item:" selectedkey="{selectedEmployeeName}"> setSelectedEmployeeName(item.key as string)}
                options={employees.map((e) => ({ key: e.name, text: e.name } as IDropdownOption))}
            />
            &lt;datepicker firstdayofweek="{DayOfWeek.Sunday}" value="{selectedDate"> selectDate(date || null)}
            />&lt;button onclick="{closeForm}">Cancel&lt;/button>&lt;button onclick="{saveCallback}">Save&lt;/button>
        &lt;/datepicker>&lt;/dropdown>&lt;/panel>
    );
}
&lt;/date>&lt;/string></textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">NewEntryFormContainer</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">employees</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">closeForm</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4"> }: </span><span style="color: #4EC9B0">NewEntryFormContainerProps</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSelectedEmployeeName</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">selectDate</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">date</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">saveCallback</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4"> || !</span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">save</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">selectedEmployeeName</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">selectedDate</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">save</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">panel</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">islightdismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">isopen</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{true}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ondismiss</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{PanelType.smallFixedFar}&quot;</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">dropdown</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">idropdownoption</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Employee&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onchanged</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{(item:&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">selectedkey</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedEmployeeName}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> setSelectedEmployeeName(item.key as string)}</span></span>
<span class="line"><span style="color: #D4D4D4">                options=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">employees</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({ </span><span style="color: #9CDCFE">key:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">text:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">IDropdownOption</span><span style="color: #D4D4D4">))</span><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">datepicker</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">firstdayofweek</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{DayOfWeek.Sunday}&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{selectedDate&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> selectDate(date || null)}</span></span>
<span class="line"><span style="color: #D4D4D4">            /&gt;</span><span style="color: #808080">&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{closeForm}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Cancel</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onclick</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;{saveCallback}&quot;</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">Save</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">datepicker</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">dropdown</span><span style="color: #808080">&gt;&lt;/</span><span style="color: #569CD6">panel</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #D4D4D4">&lt;/</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">&gt;&lt;/</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">We are now receiving the &#8220;employees&#8221; array as props to this component, instead of managing that state directly in this component via <code>useQuery()</code>. That enhances our reusability, because now we can display this component in multiple places in our application using a different data source for the employee options each time.</p>



<p class="wp-block-paragraph">But we still have a bunch of state defined in this component. This doesn&#8217;t hurt reusability, because these are the kinds of states that <em>should </em>be tightly coupled to the presentation. After all, what good is DatePicker without a corresponding piece of state to keep track of its value? Ditto for the dropdown. An exception there would be if you&#8217;re using server actions along with uncontrolled inputs, but if you&#8217;re doing that, you&#8217;re likely doing the same in each place where you&#8217;re using this component.</p>



<p class="wp-block-paragraph">The other reason this helps with reusability is that now the <em>interface</em> of this component (AKA the props) is smaller, so we don&#8217;t have to define as much state in the parent component to use this component. That results in less repeated code.</p>



<h2 id="h-come-on-dude-just-tell-me-what-to-do" class="wp-block-heading">Come on, Dude, Just Tell Me What to Do</h2>



<p class="wp-block-paragraph">There is no &#8220;always do this&#8221; or &#8220;never do that&#8221; kind of rule to take away from this article. Here are my takeaways:</p>



<ol class="wp-block-list">
<li>Having a strict <strong>separation of presentation components and container components </strong>will result in writing more boilerplate, but it has the benefit of giving you a crystal-clear scope of what needs to be rewritten if you ever decide to change your state management strategy, e.g., from Redux to hooks or hooks to Zustand.<br> &nbsp;</li>



<li>The nature of <strong>functional components and hooks lends itself to easy refactoring</strong>. If you generally follow sound component design principles, any decisions you make regarding the statefulness of a component are not permanent, and you can always change your mind.<br> &nbsp;</li>



<li><strong>Statefulness is a spectrum</strong>. It is not a dichotomy of containers and presentational components. A component can maintain some of its own state without hurting its reusability, and doing so can even increase its reusability. It is perfectly fine to get some things from props while also maintaining some state via hooks.</li>
</ol>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Modernize React State Management Beyond Redux</strong>.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in upgrading legacy React applications with hooks, React Query, and reusable component architectures.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/16056/react-smart-components-lessons-learned-while-upgrading-an-old-redux-application/">React Smart Components: Lessons Learned While Upgrading an Old Redux Application</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Embedding Private Media Files Securely in a React Frontend with Amazon S3 and AWS Lambda</title>
		<link>https://static.dmcinfo.com/blog/16076/embedding-private-media-files-securely-in-a-react-frontend-with-amazon-s3-and-aws-lambda/</link>
		
		<dc:creator><![CDATA[Sam Wallace]]></dc:creator>
		<pubDate>Wed, 31 Jul 2024 18:30:36 +0000</pubDate>
				<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/16076/embedding-private-media-files-securely-in-a-react-frontend-with-amazon-s3-and-aws-lambda/</guid>

					<description><![CDATA[<p>Amazon Simple Storage Service (S3) is a low-cost service for storing and serving unstructured data, which makes it perfect for hosting any media that will be displayed or referenced in your&#160;React frontend (PDFs, images, etc.). However, if the media fetched by your frontend is read-restricted to only authenticated users of your application, then embedding this [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16076/embedding-private-media-files-securely-in-a-react-frontend-with-amazon-s3-and-aws-lambda/">Embedding Private Media Files Securely in a React Frontend with Amazon S3 and AWS Lambda</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Amazon Simple Storage Service (S3) is a low-cost service for storing and serving unstructured data, which makes it perfect for hosting any media that will be displayed or referenced in your&nbsp;React frontend (PDFs, images, etc.). However, if the media fetched by your frontend is read-restricted to only authenticated users of your application, then embedding this content gets tricky. In this post, I&#8217;ll expand on my previous post about <a href="https://static.dmcinfo.com/latest-thinking/blog/id/12642/externally-authenticated-access-to-s3-objects-over-the-internet">externally authenticated&nbsp;access to private S3 objects</a>&nbsp;with an outline for a generalized solution to&nbsp;embedding private S3 content into your React components.</p>



<h2 id="h-object-retrieval-pattern" class="wp-block-heading">Object Retrieval Pattern</h2>



<p class="wp-block-paragraph">We&#8217;ll be using presigned S3 object URLs to fetch our private Amazon S3 data. See my previous post linked above to learn more about this design. Below is a diagram visualizing the dataflow of authorizing the React frontend to fetch an image file, using presigned URLs.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6abf20527ac17&quot;}" data-wp-interactive="core/image" data-wp-key="6abf20527ac17" class="wp-block-image size-full is-resized wp-lightbox-container"><img decoding="async" width="747" height="441" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://static.dmcinfo.com/wp-content/uploads/2024/07/object-retreival-pattern-react-frontend.png" alt="" class="wp-image-36100" style="width:480px;height:auto" srcset="https://static.dmcinfo.com/wp-content/uploads/2024/07/object-retreival-pattern-react-frontend.png 747w, https://static.dmcinfo.com/wp-content/uploads/2024/07/object-retreival-pattern-react-frontend-300x177.png 300w" sizes="(max-width: 747px) 100vw, 747px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">Click image to expand.</figcaption></figure>



<h2 id="h-initial-implementation" class="wp-block-heading">Initial Implementation</h2>



<p class="wp-block-paragraph">Let&#8217;s set up a simple component with a single <code>&lt;img&gt;</code> element that we&#8217;d like to link to our desired resource, with the resource URL passed as a prop to the component.</p>



<p class="wp-block-paragraph"></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const S3Image = ({ objectUrl }) => {

    return &lt;img src={objectUrl} alt="Image" />;

}

export default S3Image;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">S3Image</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">objectUrl</span><span style="color: #D4D4D4"> }) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">img</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">src</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">objectUrl</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">alt</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Image&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">S3Image</span><span style="color: #D4D4D4">;</span></span></code></pre></div>



<p class="wp-block-paragraph">If we want to fetch the presigned URL for the corresponding object pointed to by <code>objectUrl, </code>we need to ensure that the URL embedded into the <code>&lt;img&gt;</code> element is up to date with the object specified in the props.&nbsp;We can set up some local state to store the signed URL and a useEffect to fetch the URL&nbsp;when the <code>objectPath</code> prop&nbsp;changes.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import React, { useState, useEffect } from 'react';
const S3Image = ({ objectPath }) => {

    const &#91;signedUrl, setSignedUrl&#93; = useState('');

    useEffect( async () => {

        try {

            const response = await fetch('/get-signed-url', { objectPath });

            const data = await response.json();

            setSignedUrl(data.url);

        } catch (error) {

            console.error('Error fetching signed URL:', error);

        }

    }, &#91;objectPath&#93;);

 

  return (

      &lt;img src={signedUrl} alt="Image" />

  );

};

export default S3Image;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;react&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">S3Image</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> }) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">signedUrl</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSignedUrl</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;&apos;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">( </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">try</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;/get-signed-url&apos;</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #DCDCAA">setSignedUrl</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">error</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;Error fetching signed URL:&apos;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">img</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">src</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">signedUrl</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">alt</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Image&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">S3Image</span><span style="color: #D4D4D4">;</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">For simplicity, we&#8217;ll&nbsp;handle the signed URL&nbsp;creation in&nbsp;lambda function&nbsp;running Node.js. This assumes that an API Gateway is set up to proxy the HTTP request from the react app to this Lambda function, but this pattern could be implemented with other AWS compute resources/VPC connection methods if more applicable.&nbsp;To see more on how to implement this API Gateway + Lambda stack, see my blog on <a href="https://static.dmcinfo.com/latest-thinking/blog/id/12641/cloud-deployments-in-minutes-with-serverless-framework-and-aws-lambda">Cloud APIs in minutes with Serverless Framework and AWS Lambda</a>.</p>



<p class="wp-block-paragraph">We&#8217;ll use the AWS SDK to generate a presigned URL for this image file.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>const AWS = require('aws-sdk');

AWS.config.update({

    accessKeyId: // IAM user credentials that have access to the S3 bucket

    secretAccessKey: // IAM user secret key

    signatureVersion: 'v4',

    region: // enter your region here

});

 

const s3 = new AWS.S3();

 

module.exports = async (event) => {

    try {

        // get request parameters

        const body = JSON.parse(event.body);

       

        if(!(body.objectPath?.length)){

            return { statusCode: 400, body: JSON.stringify({error: "Invalid object path"}) };  

        }

 

        const url = s3.getSignedUrl(input.object_method, {

            Bucket: // specify the bucket we're pulling from

            Key: body.object_path,

            Expires: //specify the lifetime of the URL (in milliseconds)

        });

 

        return { statusCode: 200, body: JSON.stringify({url}) };

    } catch (error) {

        return { statusCode: 500, body: JSON.stringify(error) };

    }

}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">AWS</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">require</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;aws-sdk&apos;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #4FC1FF">AWS</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">config</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">update</span><span style="color: #D4D4D4">({</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">accessKeyId:</span><span style="color: #D4D4D4"> </span><span style="color: #6A9955">// IAM user credentials that have access to the S3 bucket</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">secretAccessKey</span><span style="color: #D4D4D4">: </span><span style="color: #6A9955">// IAM user secret key</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">signatureVersion</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&apos;v4&apos;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">region:</span><span style="color: #D4D4D4"> </span><span style="color: #6A9955">// enter your region here</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">s3</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">AWS</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">S3</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #4EC9B0">module</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">exports</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">event</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">try</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #6A9955">// get request parameters</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">body</span><span style="color: #D4D4D4"> = </span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">parse</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">event</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">body</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">       </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4">(!(</span><span style="color: #9CDCFE">body</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">?.</span><span style="color: #9CDCFE">length</span><span style="color: #D4D4D4">)){</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">statusCode:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">400</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">body:</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">stringify</span><span style="color: #D4D4D4">({</span><span style="color: #9CDCFE">error:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;Invalid object path&quot;</span><span style="color: #D4D4D4">}) };  </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">url</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">s3</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">getSignedUrl</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">input</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">object_method</span><span style="color: #D4D4D4">, {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">Bucket:</span><span style="color: #D4D4D4"> </span><span style="color: #6A9955">// specify the bucket we&apos;re pulling from</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">Key</span><span style="color: #D4D4D4">: </span><span style="color: #9CDCFE">body</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">object_path</span><span style="color: #D4D4D4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">Expires:</span><span style="color: #D4D4D4"> </span><span style="color: #6A9955">//specify the lifetime of the URL (in milliseconds)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">statusCode:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">200</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">body:</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">stringify</span><span style="color: #D4D4D4">({</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">}) };</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">statusCode:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">500</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">body:</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">stringify</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) };</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">And there it is! With this implementation alone, you&#8217;ll be able to display that image in your app, even though it&#8217;s not publicly accessible in S3.</p>



<h2 id="h-caveat-signed-url-expiration" class="wp-block-heading">Caveat &#8211; Signed URL Expiration</h2>



<p class="wp-block-paragraph">There&#8217;s one large issue with this implementation—the presigned URLs expire! Each presigned URL generated has a lifespan of a duration (configurable when the URL is generated), and after this time, requests using that&nbsp;URL will fail. This is to prevent persisting public access to the object. Remember, <em>anyone</em> with the presigned URL can use it, as long as it has not expired!</p>



<p class="wp-block-paragraph">This will not be an issue for any media fetched and displayed in the browser on page load (like our image component above), but what if you want to provide a download link to the image file? The user may linger on the page after it&#8217;s initially loaded&nbsp;and only request the file from S3 by clicking the link after the signature has expired. This leads to a nasty error screen and is probably not what your users want to see.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/url-timeout-error.png" alt=""/></figure>



<p class="wp-block-paragraph">So, we don&#8217;t want to let presigned URLs embedded into the page expire, but we also don&#8217;t want to disable the presigned URL&#8217;s timeout. To get around this, we&#8217;ll add logic to periodically re-generate the presigned URL for the given asset. Below is our component, updated with the logic to re-fetch the URL periodically (using the useInterval custom hook from the use-interval package).</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import React, { useState, useCallback } from 'react';

import useInterval from 'use-interval';

 

const S3Image = ({ objectPath }) => {

    const &#91;signedUrl, setSignedUrl&#93; = useState('');

    // define the function used to fetch the signed URL

    const getSignedUrl = useCallback(async () => {

        try {

            const response = await fetch('/get-signed-url', { objectPath });

            const data = await response.json();

            setSignedUrl(data.url);

        } catch (error) {

            console.error('Error fetching signed URL:', error);

        }

    }, &#91;objectPath&#93;);

    // call the function defined above every second

    useInterval( getSignedUrl, 1000, true );

    return &lt;img src={signedUrl} alt="Image" />;

};

export default S3Image;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useCallback</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;react&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">useInterval</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;use-interval&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">S3Image</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> }) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">signedUrl</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSignedUrl</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;&apos;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// define the function used to fetch the signed URL</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">getSignedUrl</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">try</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;/get-signed-url&apos;</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #DCDCAA">setSignedUrl</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">error</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;Error fetching signed URL:&apos;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// call the function defined above every second</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">useInterval</span><span style="color: #D4D4D4">( </span><span style="color: #9CDCFE">getSignedUrl</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">1000</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">img</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">src</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">signedUrl</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">alt</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Image&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">S3Image</span><span style="color: #D4D4D4">;</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">This implementation can be further ruggedized by modifying our Lambda function to return the&nbsp;lifetime along with the URL itself,&nbsp;such that the client knows exactly when it will need to regenerate it.&nbsp;However, regenerating the URL is not a time or compute intensive operation, so simply regenerating more often than you would ever expect the presigned URL timeout to be set to&nbsp;is most likely a reliable solution.</p>



<h2 id="h-abstracting-as-a-custom-hook" class="wp-block-heading">Abstracting as a Custom Hook</h2>



<p class="wp-block-paragraph">As a one-off use of this procedure, this implementation is perfectly suitable now. However, this logic would muddy your state management design if added in multiple locations or more complex components. To avoid this, let&#8217;s abstract this logic into a custom hook that&nbsp;provides the&nbsp;presigned URL for the S3 object path&nbsp;passed to it.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import React, { useState, useCallback } from 'react';

import useInterval from 'use-interval';

 

export const useSignedUrl = (objectPath) => {

    const &#91;signedUrl, setSignedUrl&#93; = useState('');

    const getSignedUrl = useCallback(async () => {

        try {

            const response = await fetch('/get-signed-url', { objectPath });

            const data = await response.json();

            setSignedUrl(data.url);

        } catch (error) {

            console.error('Error fetching signed URL:', error);

        }

    }, &#91;objectPath&#93;);

    useInterval( getSignedUrl, 1000, true );

    return signedUrl;

};</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useCallback</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;react&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">useInterval</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;use-interval&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useSignedUrl</span><span style="color: #D4D4D4"> = (</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #4FC1FF">signedUrl</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setSignedUrl</span><span style="color: #D4D4D4">&#93; = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;&apos;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">getSignedUrl</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">try</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">response</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;/get-signed-url&apos;</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">response</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #DCDCAA">setSignedUrl</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">error</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&apos;Error fetching signed URL:&apos;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    }, &#91;</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">&#93;);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">useInterval</span><span style="color: #D4D4D4">( </span><span style="color: #9CDCFE">getSignedUrl</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">1000</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">signedUrl</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">};</span></span></code></pre></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">This simplifies our component definition significantly.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JavaScript</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import useSignedUrl from './useSignedUrl';

const S3Image = ({ objectPath }) => {

    const signedUrl = useSignedUrl(objectPath);

    return &lt;img src={signedUrl} alt="Image" />;

};

export default S3Image;</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">useSignedUrl</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&apos;./useSignedUrl&apos;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">S3Image</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4"> }) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">signedUrl</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useSignedUrl</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">objectPath</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">img</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">src</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">signedUrl</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">alt</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;Image&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">};</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">S3Image</span><span style="color: #D4D4D4">;</span></span></code></pre></div>



<p class="wp-block-paragraph">With this, we have a clean, generalized solution for embedding restricted S3 content in our components.</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Securely Deliver Private Media in React</strong>.</h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in React, Amazon S3, and AWS Lambda for embedding protected media and our capabilities in <a href="https://static.dmcinfo.com/services/application-development/cloud-application-development/" data-type="page" data-id="710">Cloud App Dev Services</a>.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/16076/embedding-private-media-files-securely-in-a-react-frontend-with-amazon-s3-and-aws-lambda/">Embedding Private Media Files Securely in a React Frontend with Amazon S3 and AWS Lambda</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Externally Authenticated Access to S3 Objects Over the Internet</title>
		<link>https://static.dmcinfo.com/blog/16081/externally-authenticated-access-to-s3-objects-over-the-internet/</link>
		
		<dc:creator><![CDATA[Sam Wallace]]></dc:creator>
		<pubDate>Wed, 31 Jul 2024 17:11:11 +0000</pubDate>
				<category><![CDATA[Application Development]]></category>
		<category><![CDATA[Web Application Development]]></category>
		<guid isPermaLink="false">https://static.dmcinfo.com/blog/16081/externally-authenticated-access-to-s3-objects-over-the-internet/</guid>

					<description><![CDATA[<p>Amazon S3 is great for storing any type of binary data or file you may need in a centralized location in the cloud. There is a dedicated URL for each object, which can be easily shared with anyone who needs to access it. However, say that your bucket is storing private/proprietary information. You wouldn&#8217;t want [&#8230;]</p>
<p>The post <a href="https://static.dmcinfo.com/blog/16081/externally-authenticated-access-to-s3-objects-over-the-internet/">Externally Authenticated Access to S3 Objects Over the Internet</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Amazon S3 is great for storing any type of binary data or file you may need in a centralized location in the cloud. There is a dedicated URL for each object, which can be easily shared with anyone who needs to access it.</p>



<p class="wp-block-paragraph">However, say that your bucket is storing private/proprietary information. You wouldn&#8217;t want just anybody to be able to retrieve that data with an HTTP request, would you? In this blog, we&#8217;ll explore how we can securely and efficiently access Amazon S3 objects with either direct AWS or third-party authentication/authorization.</p>



<h2 id="h-bucket-policies" class="wp-block-heading">Bucket Policies</h2>



<p class="wp-block-paragraph">Bucket policies are the first step&nbsp;to restricting public access to objects. They apply to entire buckets in S3, and can be set up to only allow certain AWS IAM users, user roles, or methods of access to retrieve objects within the bucket to which it&#8217;s applied.</p>



<p class="wp-block-paragraph">Here&#8217;s a straightforward bucket policy to restrict any access to an S3 bucket except for those that provide IAM credentials that match to a specific user:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:16px 0 0 16px;width:100%;text-align:left;background-color:#1e1e1e"><span style="background:#c7c7c7;padding:0.3rem 0.5rem 0.2rem;border-radius:1rem;font-size:0.8em;line-height:1;height:1.25rem;text-align:center;display:inline-flex;align-items:center;justify-content:center;color:#1e1e1e">JSON</span></span><span role="button" tabindex="0" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::ACCOUNT_ID:user/USER_NAME"
            },
            "Action": &#91;
                "s3:GetObject"
            &#93;,
            "Resource": &#91;
                "arn:aws:s3:::BUCKET_NAME/*"
            &#93;
        }
    ]
}
</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">&quot;Version&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;2012-10-17&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">&quot;Statement&quot;</span><span style="color: #D4D4D4">: [</span></span>
<span class="line"><span style="color: #D4D4D4">        {</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">&quot;Effect&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;Allow&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">&quot;Principal&quot;</span><span style="color: #D4D4D4">: {</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #9CDCFE">&quot;AWS&quot;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;arn:aws:iam::ACCOUNT_ID:user/USER_NAME&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">            },</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">&quot;Action&quot;</span><span style="color: #D4D4D4">: &#91;</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #CE9178">&quot;s3:GetObject&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">            &#93;,</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #9CDCFE">&quot;Resource&quot;</span><span style="color: #D4D4D4">: &#91;</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #CE9178">&quot;arn:aws:s3:::BUCKET_NAME/*&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">            &#93;</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">    ]</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">Now this solution works for any situation where the client fetching the S3 object is able to provide permanent IAM credentials in their request. However, this is very rarely a valid solution. What if we have an existing&nbsp;authentication solution that we want to use to determine access to an S3 object? Or what if we don&#8217;t want/need to edit this policy whenever a new user needs access?</p>



<h2 id="h-accommodating-third-party-authentication" class="wp-block-heading">Accommodating Third-party Authentication</h2>



<p class="wp-block-paragraph">To abstract the IAM authentication layer entirely,&nbsp;we could proxy the S3 files through some intermediate compute resource&nbsp;within our VPC that:</p>



<ol class="wp-block-list">
<li>can authenticate a&nbsp;request with a 3rd party auth solution.</li>



<li>can access the buckets directly, with its own static set of IAM credentials.</li>
</ol>



<p class="wp-block-paragraph">Below is a diagram of this dataflow. Only the solid lines are data transfers that contain the data of the S3 object being requested.</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Externally-authenticated-access-to-S3-objects1.png" alt=""/></figure>



<p class="wp-block-paragraph">This is, strictly speaking, an effective solution. However, S3 objects can be&nbsp;very large, so passing the whole object through some intermediary compute resource may incur unacceptable memory/data transfer&nbsp;costs. What we&#8217;d want is a way to securely access the S3 object while still being able to pull it directly from the bucket to the end client making the request, in order to take advantage of S3&#8217;s ultra-cost-efficient retrieval pricing.</p>



<p class="wp-block-paragraph">Enter &#8211; the presigned URL!</p>



<p class="wp-block-paragraph">Presigned URLs are used to temporarily authorize operations across AWS to anybody who has the URL. To generate a presigned URL for a specific action, your IAM policy must be authorized to perform that action. To mitigate the chance that the power of a presigned URL falls into the wrong hands, they are configured to only serve their purpose for a defined timeout.</p>



<p class="wp-block-paragraph">Using presigned URLs generated to provide access to individual S3 objects at request time, we can extend the secure access approach above, such that:</p>



<ol class="wp-block-list">
<li>The client sends an authenticated request using the 3rd-party auth to a compute resource (lambda works perfectly) with full read-access to our S3 bucket.</li>



<li>The compute resource authenticates this request against 3rd party auth.</li>



<li>The compute resource generates a pre-signed URL for the S3 object requested and returns it to the client.</li>



<li>The client fetches the S3 object using the presigned URL before it times out.</li>
</ol>



<p class="wp-block-paragraph">&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://static.dmcinfo.com/wp-content/uploads/2025/05/Externally-authenticated-access-to-S3-objects2.png" alt=""/></figure>



<p class="wp-block-paragraph">Notice how the file is requested directly from S3 in this setup! With that, we have a secure and performant solution.</p>



<p class="wp-block-paragraph">To see an example implementation of this design, see my follow-up post,&nbsp;<a href="https://static.dmcinfo.com/latest-thinking/blog/id/12643/embedding-private-media-files-securely-in-a-react-frontend-with-amazon-s3-and-aws-lambda">Embedding private media files securely in a React frontend with Amazon S3 and AWS Lambda</a>.</p>



<div class="wp-block-group alignwide has-custom-light-blue-background-color has-background is-layout-flow wp-container-core-group-is-layout-dbd34961 wp-block-group-is-layout-flow" style="border-radius:20px;margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50);padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-columns alignwide are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-43efaee5 wp-block-columns-is-layout-flex" style="padding-right:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)">
<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:85%">
<h3 class="wp-block-heading has-text-align-left" id="h-have-an-upcoming-project-dmc-can-help-you-take-the-next-step"><strong>Control Access to Private S3 Content</strong></h3>



<p class="has-text-align-left wp-block-paragraph" id="h-need-help-turning-ideas-into-outcomes-automation-project-to-the-next-level-contact-us-today-to-learn-more-about-our-solutions-and-how-we-can-help-you-achieve-your-goals">Explore our <a href="https://static.dmcinfo.com/services/application-development/" data-type="page" data-id="434">Application Development</a> expertise in Amazon S3, secure object delivery, and external authentication for protecting cloud-hosted files.</p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:15%">
<div class="wp-block-buttons is-horizontal is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-2236275c wp-block-buttons-is-layout-flex">
<div class="wp-block-button is-style-fill"><a class="wp-block-button__link has-base-contrast-color has-text-color has-link-color wp-element-button" href="https://static.dmcinfo.com/contact/">Contact Us</a></div>
</div>
</div>
</div>
</div>
<p>The post <a href="https://static.dmcinfo.com/blog/16081/externally-authenticated-access-to-s3-objects-over-the-internet/">Externally Authenticated Access to S3 Objects Over the Internet</a> appeared first on <a href="https://static.dmcinfo.com/">DMC, Inc.</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
