<?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>Independent technical advice for ecommerce projects based in Yorkshire</title>
	<atom:link href="https://www.douglasradburn.co.uk/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.douglasradburn.co.uk/</link>
	<description>Independent technical advice for ecommerce projects</description>
	<lastBuildDate>Tue, 29 Sep 2026 14:29:35 +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>
	<item>
		<title>What Happens During a Shopify Page Request?</title>
		<link>https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/</link>
					<comments>https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 14:29:34 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<guid isPermaLink="false">https://www.douglasradburn.co.uk/?p=1957</guid>

					<description><![CDATA[<p>Understanding the End-to-End Journey from URL to Rendered Storefront When we talk about Shopify performance, most discussions focus on Lighthouse scores, image optimisation or JavaScript bundles. Relatively few people take <a class="read-more" href="https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/">What Happens During a Shopify Page Request?</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">Understanding the End-to-End Journey from URL to Rendered Storefront</h3>



<p class="wp-block-paragraph">When we talk about Shopify performance, most discussions focus on Lighthouse scores, image optimisation or JavaScript bundles. </p>



<p class="wp-block-paragraph">Relatively few people take the time to understand what actually happens when a customer visits a Shopify page.</p>



<p class="wp-block-paragraph">Every product page, collection page and landing page goes through a complex request lifecycle involving DNS resolution, CDN delivery, server-side Liquid rendering, browser processing and asset loading before the customer sees a fully interactive storefront.</p>



<p class="wp-block-paragraph">For architects, developers and technical leaders, understanding this journey can help explain where performance bottlenecks originate, why certain optimisation techniques work, and where effort should be focused.</p>



<h2 class="wp-block-heading">The High-Level Journey</h2>



<p class="wp-block-paragraph">At a simplified level, a Shopify page request follows this flow:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="238" height="1024" src="https://www.douglasradburn.co.uk/wp-content/uploads/architecture-of-a-page-request-238x1024.png" alt="" class="wp-image-1969" style="width:239px;height:auto" srcset="https://www.douglasradburn.co.uk/wp-content/uploads/architecture-of-a-page-request-238x1024.png 238w, https://www.douglasradburn.co.uk/wp-content/uploads/architecture-of-a-page-request-186x800.png 186w" sizes="(max-width: 238px) 100vw, 238px" /></figure>
</div>


<p class="wp-block-paragraph">While this appears straight forward, a simple request has at least 13 layers to through; each stage contains multiple layers of processing.</p>



<h2 class="wp-block-heading">Stage 1: DNS Resolution</h2>



<p class="wp-block-paragraph">Everything starts when a customer enters a URL or clicks a link.</p>



<p class="wp-block-paragraph">Before Shopify can serve any content, the browser must determine where the website lives.</p>



<p class="wp-block-paragraph">The browser performs:</p>



<ul class="wp-block-list">
<li>DNS lookup</li>



<li>Connection establishment</li>



<li>TLS negotiation</li>



<li>HTTP/2 or HTTP/3 connection setup</li>
</ul>



<p class="wp-block-paragraph">Shopify&#8217;s platform documentation notes that storefront traffic is delivered through Shopify&#8217;s global CDN infrastructure, backed by Cloudflare, and uses modern protocols including HTTP/3 and TLS 1.3 to improve both performance and security. See more here: <a href="https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform">https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform</a></p>



<p class="wp-block-paragraph">This process typically completes in milliseconds, but geographical location, network quality and DNS configuration all influence the result, and downtime with Cloudflare means downtime for your site.</p>



<h2 class="wp-block-heading">Stage 2: Request Reaches Shopify&#8217;s Edge</h2>



<p class="wp-block-paragraph">Once the connection is established, the request reaches Shopify&#8217;s edge infrastructure. The edge layer acts as the first point of contact for every storefront request and provides:</p>



<ul class="wp-block-list">
<li>Traffic routing</li>



<li>Distributed delivery</li>



<li>Cache management</li>



<li>Compression</li>



<li>Security controls</li>



<li>DDoS protection</li>
</ul>



<p class="wp-block-paragraph">Shopify serves assets through its CDN using same-origin URLs under the store&#8217;s own domain. This avoids additional DNS lookups and connection overhead that can occur when assets are hosted separately.</p>



<p class="wp-block-paragraph">For static assets such as:</p>



<ul class="wp-block-list">
<li>Images</li>



<li>CSS</li>



<li>JavaScript</li>



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



<p class="wp-block-paragraph">The CDN may be able to serve content immediately without further processing. Dynamic storefront pages however require additional work.</p>



<h2 class="wp-block-heading">Stage 3: Storefront Renderer Takes Over</h2>



<p class="wp-block-paragraph">For dynamic content, Shopify&#8217;s Storefront Renderer (SFR) processes the request.</p>



<p class="wp-block-paragraph">Shopify describes Storefront Renderer as a dedicated server-side rendering system responsible for handling storefront requests as quickly as possible. This is where Shopify begins constructing the page.</p>



<p class="wp-block-paragraph">The renderer must determine:</p>



<ul class="wp-block-list">
<li>Which page has been requested.</li>



<li>Which template should be used.</li>



<li>Which store data is required.</li>



<li>Which sections and snippets need rendering.</li>
</ul>



<p class="wp-block-paragraph">Unlike client-heavy frameworks, most Shopify storefronts still generate their core HTML on the server.</p>



<p class="wp-block-paragraph">This means customers receive a fully rendered document rather than a blank page waiting for JavaScript execution.</p>



<h2 class="wp-block-heading">Stage 4: URL Routing</h2>



<p class="wp-block-paragraph">The renderer analyses the requested URL and identifies the associated resource.</p>



<p class="wp-block-paragraph">For example:</p>



<pre class="wp-block-code"><code>/products/leather-wallet</code></pre>



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



<pre class="wp-block-code"><code>/products/leather-wallet</code></pre>



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



<pre class="wp-block-code"><code>/collections/new-arrivals</code></pre>



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



<pre class="wp-block-code"><code>Collection Resource</code></pre>



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



<pre class="wp-block-code"><code>/pages/about-us

/blogs/news

/search

/cart</code></pre>



<p class="wp-block-paragraph">all map to different resource types.</p>



<p class="wp-block-paragraph">This routing process determines which data models and templates Shopify will use next.</p>



<h2 class="wp-block-heading">Stage 5: Template Selection</h2>



<p class="wp-block-paragraph">Once Shopify knows the resource being requested, it identifies the appropriate template.</p>



<p class="wp-block-paragraph">Modern Online Store 2.0 themes primarily use JSON templates.</p>



<p class="wp-block-paragraph">Shopify&#8217;s architecture documentation explains that templates define which sections should appear on a page, while layouts provide the broader page structure around them.  Eg;</p>



<pre class="wp-block-code"><code>product.json</code></pre>



<p class="wp-block-paragraph">may contain:</p>



<ul class="wp-block-list">
<li>Product Section</li>



<li>Product Recommendations</li>



<li>Recently Viewed Products</li>



<li>FAQ Section</li>
</ul>



<p class="wp-block-paragraph">All configured in a merchant-editable structure. This flexibility is one of the major enhancements introduced by Online Store 2.0.</p>



<h2 class="wp-block-heading">Stage 6: Data Loading</h2>



<p class="wp-block-paragraph">With the template selected, Shopify loads the necessary objects.</p>



<p class="wp-block-paragraph">Depending on the page, this may include:</p>



<h3 class="wp-block-heading">Product Pages</h3>



<ul class="wp-block-list">
<li>Product data.</li>



<li>Variants.</li>



<li>Images.</li>



<li>Metafields.</li>



<li>Inventory availability.</li>
</ul>



<h3 class="wp-block-heading">Collection Pages</h3>



<ul class="wp-block-list">
<li>Collection information.</li>



<li>Product listings.</li>



<li>Filters.</li>



<li>Sort orders.</li>
</ul>



<h3 class="wp-block-heading">Global Objects</h3>



<ul class="wp-block-list">
<li>Shop settings.</li>



<li>Theme settings.</li>



<li>Customer details.</li>



<li>Locale information.</li>



<li>Market configuration.</li>
</ul>



<p class="wp-block-paragraph">Shopify loads these objects before Liquid rendering begins. The amount of data requested here can have a direct impact on performance. Stores with large numbers of metafields, excessive looping, or overly complex rendering logic often experience increased server-side render times.</p>



<h2 class="wp-block-heading">Stage 7: Liquid Rendering</h2>



<p class="wp-block-paragraph">This is where the magic happens.</p>



<p class="wp-block-paragraph">Liquid is Shopify&#8217;s server-side templating language.</p>



<p class="wp-block-paragraph">The rendering engine combines the following into a complete HTML document.:</p>



<ul class="wp-block-list">
<li>Templates.</li>



<li>Sections.</li>



<li>Blocks.</li>



<li>Snippets.</li>



<li>Shopify data objects.</li>
</ul>



<p class="wp-block-paragraph">Shopify&#8217;s theme architecture consists of the following which all participate in generating the final page.:</p>



<ul class="wp-block-list">
<li>Layouts.</li>



<li>Templates.</li>



<li>Section Groups.</li>



<li>Sections.</li>



<li>Blocks.</li>



<li>Snippets.</li>
</ul>



<p class="wp-block-paragraph">For example:</p>



<pre class="wp-block-code"><code>{% render 'product-card', product: product %}</code></pre>



<p class="wp-block-paragraph">renders reusable snippet content into the page. Shopify&#8217;s <code>render</code> tag is specifically designed to generate snippets safely and consistently during server-side rendering. </p>



<p class="wp-block-paragraph"><strong>At this stage the customer still hasn&#8217;t received any HTML.</strong></p>



<p class="wp-block-paragraph">Everything is happening inside Shopify&#8217;s infrastructure.</p>



<h2 class="wp-block-heading">Stage 8: HTML Response Sent to Browser</h2>



<p class="wp-block-paragraph">After rendering completes, Shopify returns the generated HTML.</p>



<p class="wp-block-paragraph">This response contains:</p>



<ul class="wp-block-list">
<li>Fully rendered markup.</li>



<li>CSS references.</li>



<li>JavaScript references.</li>



<li>Image URLs.</li>



<li>Metadata.</li>



<li>Structured data.</li>



<li>Analytics integrations / 3rd Party Scripts.</li>
</ul>



<p class="wp-block-paragraph">The faster Shopify can complete rendering, the lower the Time To First Byte (TTFB).</p>



<p class="wp-block-paragraph">This is why inefficient Liquid logic can become a performance issue. Liquid executes before the HTML reaches the browser, meaning poor server-side implementation increases wait time for every visitor.</p>



<h2 class="wp-block-heading">Stage 9: Browser Parsing Begins</h2>



<p class="wp-block-paragraph">Once HTML arrives, the browser begins creating:</p>



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



<p class="wp-block-paragraph">A representation of the page structure.</p>



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



<p class="wp-block-paragraph">A representation of all styling rules.</p>



<p class="wp-block-paragraph">The browser then starts assembling the Render Tree. At this point the page may begin appearing on screen before all assets have completed loading. This perceived performance is often more important than total page load time.</p>



<h2 class="wp-block-heading">Stage 10: Asset Requests</h2>



<p class="wp-block-paragraph">The browser discovers additional resources and requests them.</p>



<p class="wp-block-paragraph">Common examples include:</p>



<ul class="wp-block-list">
<li>CSS files.</li>



<li>JavaScript bundles.</li>



<li>Fonts.</li>



<li>Product imagery.</li>



<li>Videos.</li>



<li>Third-party tags.</li>
</ul>



<p class="wp-block-paragraph">Most of these assets are delivered directly from Shopify&#8217;s CDN.</p>



<p class="wp-block-paragraph">For imagery specifically, Shopify can dynamically:</p>



<ul class="wp-block-list">
<li>Resize images.</li>



<li>Convert image formats.</li>



<li>Compress assets.</li>



<li>Deliver AVIF/WebP where supported.</li>
</ul>



<p class="wp-block-paragraph">This is one reason properly implemented Shopify themes can achieve excellent image performance.</p>



<h2 class="wp-block-heading">Stage 11: JavaScript Execution</h2>



<p class="wp-block-paragraph">After downloading JavaScript assets, the browser begins execution.</p>



<p class="wp-block-paragraph">This is often where performance challenges emerge.</p>



<p class="wp-block-paragraph">Common offenders include:</p>



<ul class="wp-block-list">
<li>Search integrations.</li>



<li>Review applications.</li>



<li>Personalisation engines.</li>



<li>Loyalty platforms.</li>



<li>Analytics tools.</li>



<li>Tag managers.</li>
</ul>



<p class="wp-block-paragraph">Many organisations spend significant effort optimising images while overlooking the impact of dozens of third-party scripts running simultaneously.</p>



<p class="wp-block-paragraph">In practice, excessive JavaScript is often a larger source of performance degradation than Shopify itself.</p>



<h2 class="wp-block-heading">Stage 12: Hydration and Customer Interaction</h2>



<p class="wp-block-paragraph">Modern Shopify themes now contain far more interactive components than traditional ecommerce platforms.</p>



<p class="wp-block-paragraph">Examples include:</p>



<ul class="wp-block-list">
<li>Predictive search.</li>



<li>Variant selection.</li>



<li>Media galleries.</li>



<li>Dynamic carts.</li>



<li>Recommendation engines.</li>
</ul>



<p class="wp-block-paragraph">Once JavaScript completes execution, these elements become interactive.</p>



<p class="wp-block-paragraph">The page is now considered fully usable.</p>



<h2 class="wp-block-heading">Why This Matters</h2>



<p class="wp-block-paragraph">Understanding the Shopify page request lifecycle helps teams identify where performance problems actually originate.</p>



<p class="wp-block-paragraph">A slow page could be caused by:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th>Layer</th><th>Potential Issue</th></tr><tr><td>DNS</td><td>Poor domain configuration</td></tr><tr><td>CDN</td><td>Cache misses</td></tr><tr><td>Liquid Rendering</td><td>Heavy loops, excessive metafields</td></tr><tr><td>Data Layer</td><td>Over-fetching content</td></tr><tr><td>Images</td><td>Large unoptimised assets</td></tr><tr><td>JavaScript</td><td>Third-party app bloat</td></tr><tr><td>Browser</td><td>Render-blocking resources</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">The key takeaway is that Shopify performance is rarely determined by a single factor.</p>



<p class="wp-block-paragraph">A customer experiences the combined effect of:</p>



<ul class="wp-block-list">
<li>Network latency</li>



<li>Platform performance</li>



<li>Theme architecture</li>



<li>Asset optimisation</li>



<li>Third-party dependencies</li>



<li>Browser execution</li>
</ul>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">A Shopify page request is far more than a simple webpage download. Between the initial DNS lookup and the final interactive storefront, the request passes through Shopify&#8217;s global CDN, Storefront Renderer, theme architecture, Liquid rendering engine and browser rendering pipeline.</p>



<p class="wp-block-paragraph">For technical teams, understanding this lifecycle provides a framework for diagnosing performance issues and making better architectural decisions. Whether you&#8217;re optimising Core Web Vitals, reducing TTFB, auditing app performance or redesigning a theme architecture, the most successful improvements come from understanding where time is actually being spent during the request journey.</p>



<p class="wp-block-paragraph">Put simply: <strong>before you can optimise a Shopify storefront, you need to understand how Shopify builds it.</strong></p>
<p>The post <a href="https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/">What Happens During a Shopify Page Request?</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/what-happens-during-a-shopify-page-request/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PDPs Are Becoming the New PLPs: Why Product Pages Are Taking Over Product Discovery</title>
		<link>https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/</link>
					<comments>https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 10:12:08 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<guid isPermaLink="false">https://www.douglasradburn.co.uk/?p=1971</guid>

					<description><![CDATA[<p>For years, ecommerce websites followed a relatively simple model. Product Listing Pages (PLPs) / Categories were designed for browsing and discovery, while Product Detail Pages (PDPs) existed to help customers <a class="read-more" href="https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/">PDPs Are Becoming the New PLPs: Why Product Pages Are Taking Over Product Discovery</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For years, ecommerce websites followed a relatively simple model. Product Listing Pages (PLPs) / Categories were designed for browsing and discovery, while Product Detail Pages (PDPs) existed to help customers make a final purchasing decision. Customers would navigate through categories, apply filters, compare products, and eventually land on a PDP where they could view key information, select options, and add an item to their basket.</p>



<p class="wp-block-paragraph">Today, that separation is rapidly disappearing.</p>



<p class="wp-block-paragraph">Modern ecommerce retailers are increasingly transforming their PDPs into powerful merchandising environments packed with product recommendations, outfit inspiration, bundles, upsell opportunities, and related products. Rather than acting as the end point of the buying journey, PDPs are becoming destinations in their own right, encouraging continued browsing and product discovery.</p>



<p class="wp-block-paragraph">Fashion retailer Next provides an excellent example of this shift. Visit a typical product page and you&#8217;ll often find substantial sections dedicated to coordinating items, similar products, recommended purchases, and alternative styles. In some cases, these merchandising components occupy more space than the product&#8217;s core information itself.</p>



<p class="wp-block-paragraph">This evolution represents one of the most significant changes in ecommerce merchandising over the past decade.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="608" height="2560" src="https://www.douglasradburn.co.uk/wp-content/uploads/next-website-scaled.png" alt="" class="wp-image-1973" srcset="https://www.douglasradburn.co.uk/wp-content/uploads/next-website-scaled.png 608w, https://www.douglasradburn.co.uk/wp-content/uploads/next-website-190x800.png 190w, https://www.douglasradburn.co.uk/wp-content/uploads/next-website-243x1024.png 243w, https://www.douglasradburn.co.uk/wp-content/uploads/next-website-365x1536.png 365w" sizes="(max-width: 608px) 100vw, 608px" /></figure>
</div>


<h2 class="wp-block-heading">The Traditional Role of the PDP</h2>



<p class="wp-block-paragraph">Historically, the purpose of a PDP was straightforward: convert interest into a sale.</p>



<p class="wp-block-paragraph">Product pages typically contained:</p>



<ul class="wp-block-list">
<li>Product images</li>



<li>Descriptions and specifications</li>



<li>Pricing information</li>



<li>Variant selection</li>



<li>Delivery and returns details</li>



<li>Add to basket functionality</li>



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



<p class="wp-block-paragraph">Any product recommendations were generally treated as secondary content. A &#8220;Customers Also Bought&#8221; carousel might appear at the bottom of the page, but it played a supporting role to the primary objective of helping customers evaluate a specific product.</p>



<p class="wp-block-paragraph">The underlying assumption was that product discovery had already happened elsewhere.</p>



<p class="wp-block-paragraph">Customers discovered products on:</p>



<ul class="wp-block-list">
<li>Category pages</li>



<li>Search results</li>



<li>Promotional landing pages</li>



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



<p class="wp-block-paragraph">By the time they reached a PDP, the retailer&#8217;s job was simply to remove friction from the purchase decision.</p>



<p class="wp-block-paragraph">That assumption no longer reflects how customers actually shop.</p>



<h2 class="wp-block-heading">The Modern Customer Journey Is Far Less Linear</h2>



<p class="wp-block-paragraph">One of the biggest drivers behind this trend is the changing nature of customer acquisition.</p>



<p class="wp-block-paragraph">Increasingly, visitors arrive directly on product pages rather than entering through category structures.</p>



<p class="wp-block-paragraph">Customers may land on a PDP from:</p>



<ul class="wp-block-list">
<li>Google Shopping results</li>



<li>Organic search</li>



<li>Social media platforms</li>



<li>Influencer content</li>



<li>Email campaigns</li>



<li>Affiliate links</li>



<li>AI-powered search experiences &amp; LLMS</li>
</ul>



<p class="wp-block-paragraph">In many cases, the PDP is the first page a customer sees.</p>



<p class="wp-block-paragraph">This creates a challenge for retailers. If a visitor enters the site halfway through the traditional journey, how do you expose them to the wider product catalogue?</p>



<p class="wp-block-paragraph">The answer is simple: bring the discovery experience to the PDP.</p>



<p class="wp-block-paragraph">Rather than forcing customers back to category pages, retailers now surface relevant products directly alongside the item they&#8217;re currently viewing.</p>



<h2 class="wp-block-heading">The Rise of the PDP as a Merchandising Hub</h2>



<p class="wp-block-paragraph">The best modern product pages no longer focus solely on selling a single item. Instead, they attempt to maximise the commercial value of every site visit.</p>



<p class="wp-block-paragraph">This has fundamentally changed how merchandising teams think about PDP design.</p>



<p class="wp-block-paragraph">Using retailers such as Next as an example, a typical product page may now include:</p>



<h3 class="wp-block-heading">Complete the Look</h3>



<p class="wp-block-paragraph">Particularly common in fashion retail, these modules present complementary products chosen to create a full outfit.</p>



<p class="wp-block-paragraph">Instead of purchasing a shirt, customers are encouraged to buy:</p>



<ul class="wp-block-list">
<li>Trousers</li>



<li>Shoes</li>



<li>Jackets</li>



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



<p class="wp-block-paragraph">The retailer is no longer selling a product. They&#8217;re selling a solution.</p>



<h3 class="wp-block-heading">Similar Products</h3>



<p class="wp-block-paragraph">Many PDPs now include substantial grids of comparable items.</p>



<p class="wp-block-paragraph">These may feature:</p>



<ul class="wp-block-list">
<li>Alternative colours</li>



<li>Different fits</li>



<li>Similar styles</li>



<li>Alternative brands</li>



<li>Different price points</li>
</ul>



<p class="wp-block-paragraph">Functionally, this mirrors the role traditionally performed by a category page.</p>



<h3 class="wp-block-heading">Frequently Bought Together</h3>



<p class="wp-block-paragraph">Cross-selling modules encourage customers to purchase related items alongside their primary purchase.</p>



<p class="wp-block-paragraph">For example:</p>



<ul class="wp-block-list">
<li>Trainers with cleaning kits</li>



<li>Laptops with accessories</li>



<li>Furniture with matching products</li>
</ul>



<p class="wp-block-paragraph">These recommendations are designed specifically to increase basket value.</p>



<h3 class="wp-block-heading">Recently Viewed Products</h3>



<p class="wp-block-paragraph">This allows customers to continue product exploration without needing to return to a PLP.</p>



<p class="wp-block-paragraph">The result is a browsing experience that feels more fluid and less constrained by traditional site architecture.</p>



<h2 class="wp-block-heading">Why Retailers Are Investing So Heavily in PDP Merchandising</h2>



<p class="wp-block-paragraph">The answer comes down to economics.</p>



<p class="wp-block-paragraph">Customer acquisition costs continue to rise across virtually every digital marketing channel.</p>



<p class="wp-block-paragraph">Retailers are spending significant sums on:</p>



<ul class="wp-block-list">
<li>Paid search</li>



<li>Paid social</li>



<li>Influencer partnerships</li>



<li>Display advertising</li>



<li>Affiliate networks</li>
</ul>



<p class="wp-block-paragraph">When attracting visitors becomes more expensive, increasing revenue per visitor becomes a strategic priority.</p>



<p class="wp-block-paragraph">As a result, retailers are placing much greater emphasis on metrics such as:</p>



<ul class="wp-block-list">
<li>Average Order Value (AOV)</li>



<li>Revenue per Session</li>



<li>Units per Transaction</li>



<li>Basket Size</li>
</ul>



<p class="wp-block-paragraph">Every PDP view becomes an opportunity not just to secure a purchase, but to increase the overall value of that purchase.</p>



<p class="wp-block-paragraph">The mindset shifts from:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>&#8220;How do we sell this product?&#8221;</em></p>
</blockquote>



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



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>&#8220;How do we maximise the value of this customer session?&#8221;</em></p>
</blockquote>



<h2 class="wp-block-heading">Technology Is Enabling Smarter Recommendations</h2>



<p class="wp-block-paragraph">The growth of personalisation technologies and recommendation engines has accelerated this trend dramatically.</p>



<p class="wp-block-paragraph">Historically, related products were often manually curated by merchandising teams.</p>



<p class="wp-block-paragraph">Today, recommendation engines can analyse the following to determine which products should appear in recommendation zones:</p>



<ul class="wp-block-list">
<li>Customer behaviour</li>



<li>Purchase history</li>



<li>Browsing patterns</li>



<li>Category affinities</li>



<li>Product relationships</li>



<li>Profit margins</li>



<li>Inventory availability</li>
</ul>



<p class="wp-block-paragraph">This creates a much more dynamic experience.</p>



<p class="wp-block-paragraph">Two customers viewing the same product may see entirely different recommendations based on their behaviour, interests, or previous purchases.</p>



<p class="wp-block-paragraph">As AI-powered personalisation continues to mature, PDPs are becoming increasingly individualised rather than serving as static templates.</p>



<h2 class="wp-block-heading">The Benefits for Customers</h2>



<p class="wp-block-paragraph">Although the trend is commercially motivated, it isn&#8217;t solely beneficial for retailers.</p>



<p class="wp-block-paragraph">When implemented well, customers also gain a better shopping experience.</p>



<h3 class="wp-block-heading">Faster Discovery</h3>



<p class="wp-block-paragraph">Customers can explore alternative products without repeatedly navigating back through category structures.</p>



<h3 class="wp-block-heading">Better Inspiration</h3>



<p class="wp-block-paragraph">Fashion retailers can showcase complete looks rather than individual products, helping shoppers visualise how items work together.</p>



<h3 class="wp-block-heading">Reduced Effort</h3>



<p class="wp-block-paragraph">Relevant recommendations remove the need for customers to perform multiple searches to find complementary products.</p>



<h3 class="wp-block-heading">Improved Mobile Experiences</h3>



<p class="wp-block-paragraph">On mobile devices, moving between category pages and product pages can feel cumbersome.</p>



<p class="wp-block-paragraph">Integrated discovery modules create a smoother browsing experience that requires fewer taps and screen transitions.</p>



<h2 class="wp-block-heading">The Risks of Over-Merchandising</h2>



<p class="wp-block-paragraph">Despite the benefits, there is a danger that some retailers may take the concept too far.</p>



<p class="wp-block-paragraph">The more recommendation modules added to a PDP, the greater the risk of distracting customers from the primary conversion goal.</p>



<p class="wp-block-paragraph">Potential issues include:</p>



<ul class="wp-block-list">
<li>Decision paralysis</li>



<li>Cognitive overload</li>



<li>Reduced focus on the featured product</li>



<li>Longer purchase journeys</li>



<li>Lower page performance</li>
</ul>



<p class="wp-block-paragraph">Many retailers are also introducing additional JavaScript and third-party recommendation services, which can negatively impact Core Web Vitals and overall site speed.</p>



<p class="wp-block-paragraph">A poorly optimised PDP can feel less like a curated shopping experience and more like an endless stream of competing recommendations.</p>



<p class="wp-block-paragraph">The challenge is finding the right balance.</p>



<h2 class="wp-block-heading">The Future of Ecommerce Product Pages</h2>



<p class="wp-block-paragraph">The distinction between Product Listing Pages and Product Detail Pages is becoming increasingly blurred.</p>



<p class="wp-block-paragraph">As acquisition costs rise and personalisation technologies improve, ecommerce retailers are transforming PDPs into sophisticated discovery and merchandising environments that extend far beyond their traditional purpose.</p>



<p class="wp-block-paragraph">The most successful retailers will be those that strike the right balance between helping customers make a purchasing decision and encouraging further exploration of the catalogue.</p>



<p class="wp-block-paragraph">Rather than viewing PLPs and PDPs as separate stages of the customer journey, brands are beginning to treat them as complementary experiences that can fulfil many of the same objectives.</p>



<p class="wp-block-paragraph">In many ways, the modern PDP has become a highly focused, highly personalised Product Listing Page.</p>



<p class="wp-block-paragraph">The future of ecommerce is unlikely to be defined by a strict separation between browsing and buying. Instead, it will be characterised by pages that seamlessly combine both, helping customers discover more products while simultaneously moving them closer to conversion. The PDP is no longer the final stop on the journey. Increasingly, it&#8217;s where the journey truly begins.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/">PDPs Are Becoming the New PLPs: Why Product Pages Are Taking Over Product Discovery</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/pdps-are-becoming-the-new-plps-why-product-pages-are-taking-over-product-discovery/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Luxury Without Lag: How to Optimise Images on Shopify for Speed and Impact</title>
		<link>https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/</link>
					<comments>https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Wed, 12 Aug 2026 16:25:17 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Shopify]]></category>
		<category><![CDATA[cdn]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[images]]></category>
		<category><![CDATA[liquid]]></category>
		<category><![CDATA[shopify]]></category>
		<guid isPermaLink="false">https://www.douglasradburn.co.uk/?p=1910</guid>

					<description><![CDATA[<p>Image-led ecommerce has become the default expectation for premium, lifestyle and luxury brands. On modern Shopify stores, imagery is no longer just supporting content; it is often the primary way <a class="read-more" href="https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/">Luxury Without Lag: How to Optimise Images on Shopify for Speed and Impact</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Image-led ecommerce has become the default expectation for premium, lifestyle and luxury brands. On modern Shopify stores, imagery is no longer just supporting content; it is often the primary way a brand communicates quality, emotion, craftsmanship and aspiration. Large editorial banners, full-bleed campaign photography, product galleries, lifestyle content, lookbooks, shoppable images and video-like visual storytelling are now common patterns across high-end ecommerce experiences.</p>



<p class="wp-block-paragraph">For luxury and premium brands in particular, an ecommerce site has to feel as considered as the product itself. Design commentary across luxury ecommerce regularly highlights high-quality photography, elegant visual hierarchy, minimalist layouts, strong storytelling and seamless user journeys as defining characteristics of premium digital experiences. Sites in this space often use large hero imagery, full-screen video or photography-led landing pages, refined typography and spacious layouts to create a sense of exclusivity and emotional connection.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="699" src="https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-1024x699.png" alt="" class="wp-image-1914" srcset="https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-1024x699.png 1024w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-500x341.png 500w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-768x524.png 768w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-1536x1048.png 1536w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.52.37-2048x1397.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="620" src="https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-1024x620.png" alt="" class="wp-image-1915" srcset="https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-1024x620.png 1024w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-500x303.png 500w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-768x465.png 768w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-1536x931.png 1536w, https://www.douglasradburn.co.uk/wp-content/uploads/Screenshot-2026-06-25-at-20.48.40-2048x1241.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Image-heavy stores introduce a performance challenge: every visual asset has a cost. Large unoptimised images increase page weight, slow down Largest Contentful Paint, delay perceived load time and can negatively affect conversion, mobile experience and search performance.</p>



<p class="wp-block-paragraph">Shopify provides a strong foundation through its CDN and image transformation capabilities, but clients still need to prepare, size and manage assets carefully before upload.</p>



<h2 class="wp-block-heading">Why Image Optimisation Matters on Shopify</h2>



<p class="wp-block-paragraph">Images are often among the heaviest assets on ecommerce pages, especially on product detail pages, collection pages, campaign landing pages and homepage hero sections. Shopify’s own theme performance guidance states that performance directly influences conversion rates, repeat business and search engine rankings. Shopify also advises theme developers to build with performance in mind, with image loading, JavaScript usage and rendering behaviour all contributing to storefront speed.</p>



<p class="wp-block-paragraph">For image-led brands, the goal is not to reduce visual quality to the point where the experience feels compromised. The goal is to deliver the <strong>right image</strong>, at the <strong>right dimensions</strong>, in the <strong>right format</strong>, at the <strong>right time</strong>.</p>



<p class="wp-block-paragraph">A luxury homepage still needs rich photography, but a mobile visitor should not be forced to download a 6000px desktop campaign image, as the rendered viewport only needs a much smaller version.</p>



<p class="wp-block-paragraph">Good Shopify image optimisation improves:</p>



<ul class="wp-block-list">
<li><strong>Page speed</strong> — smaller and better-sized images download faster.</li>



<li><strong>Largest Contentful Paint</strong> — hero images are often the LCP element.</li>



<li><strong>Mobile experience</strong> — smaller assets matter more on constrained networks.</li>



<li><strong>SEO</strong> — performance and well-written alt text both support discoverability.</li>



<li><strong>Conversion</strong> — faster browsing reduces friction in product discovery.</li>



<li><strong>Operational consistency</strong> — agreed upload standards reduce ad hoc performance issues over time.</li>
</ul>



<h2 class="wp-block-heading">How Shopify’s CDN Delivers Image Content</h2>



<p class="wp-block-paragraph">Shopify includes a global content delivery network for storefront assets. Its CDN is designed to host, transform, compress and deliver image files quickly from distributed edge infrastructure. Shopify describes its CDN as supporting file compression, file format conversion and dynamic editing, including real-time transformations such as resizing and cropping without destructively changing the original uploaded asset.</p>



<p class="wp-block-paragraph">When an image is uploaded to Shopify, the original file is stored and can then be requested in different transformed variants.</p>



<p class="wp-block-paragraph">Theme code can use Shopify’s Liquid image filters, particularly <code>image_url</code>, to request a specific width, height, crop or other transformation. You can find a list of attributes that you can use within Liquid to do this; <a href="https://shopify.dev/docs/api/liquid/filters/image_url">https://shopify.dev/docs/api/liquid/filters/image_url</a></p>



<p class="wp-block-paragraph">Shopify can also serve modern image formats where supported. Shopifyautomatically detects supported client-side file formats and can deliver WebP or AVIF where supported, with JPG as a fallback for other clients. This means the browser’s capabilities can influence the final file delivered, helping reduce transfer size without merchants having to manually create every format variant.</p>



<p class="wp-block-paragraph">A simplified request flow looks like this:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="843" height="1024" src="https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-843x1024.png" alt="" class="wp-image-1919" srcset="https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-843x1024.png 843w, https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-500x607.png 500w, https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-768x932.png 768w, https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-1265x1536.png 1265w, https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow-1687x2048.png 1687w, https://www.douglasradburn.co.uk/wp-content/uploads/shopify-cdn-image-workflow.png 1868w" sizes="auto, (max-width: 843px) 100vw, 843px" /></figure>



<p class="wp-block-paragraph">In practical Shopify theme development, this matters because the theme should not simply output the original uploaded file everywhere. Instead, it should request appropriately sized variants. For example:</p>



<pre class="wp-block-code"><code>{{ product.featured_image | image_url: width: 800 }}</code></pre>



<p class="wp-block-paragraph">This asks Shopify’s CDN for an 800px-wide version of the image. Shopify’s <code>image_url</code> documentation confirms that if only width is specified, the height is automatically calculated based on the image’s dimensions.</p>



<p class="wp-block-paragraph">For responsive images, developers can combine Shopify’s image filters with <code>srcset</code> and <code>sizes</code>, allowing the browser to select the most appropriate image for the user’s viewport and device pixel ratio. Shopify has long recommended responsive image approaches as a way to reduce page loading times, particularly because mobile visitors do not need to download desktop-sized images.</p>



<pre class="wp-block-code"><code>&lt;img
  srcset="
    {% if image.width &gt;= 350 %}{{ image | image_url: width: 350 }} 350w,{% endif %}
    {% if image.width &gt;= 750 %}{{ image | image_url: width: 750 }} 750w,{% endif %}
    {% if image.width &gt;= 1100 %}{{ image | image_url: width: 1100 }} 1100w,{% endif %}
    {% if image.width &gt;= 1500 %}{{ image | image_url: width: 1500 }} 1500w,{% endif %}
    {{ image | image_url }} {{ image.width }}w
  "
  src="{{ image | image_url: width: 1100 }}"
  loading="eager"
  fetchpriority="high"
  width="{{ image.width }}"
  height="{{ image.height }}"
  alt="{{ image.alt | escape }}"
&gt;</code></pre>



<h2 class="wp-block-heading">What Shopify Optimises Automatically — and What It Does Not</h2>



<p class="wp-block-paragraph">Shopify gives merchants a good baseline, but it is not a complete substitute for good asset preparation.</p>



<p class="wp-block-paragraph">Shopify can help with:</p>



<ul class="wp-block-list">
<li><strong>CDN delivery</strong> from distributed infrastructure.</li>



<li><strong>Automatic compression</strong> to reduce file size without visible quality loss in most cases.</li>



<li><strong>Dynamic resizing and cropping</strong> using URL/Liquid parameters.</li>



<li><strong>Modern format delivery</strong>, including WebP or AVIF where the client supports it.</li>



<li><strong>Preservation of the original file</strong>, so that transformations are non-destructive.</li>
</ul>



<p class="wp-block-paragraph">However, clients and implementation teams still need to manage:</p>



<ul class="wp-block-list">
<li>The size and quality of the original uploaded asset.</li>



<li>Whether the theme requests sensible image dimensions.</li>



<li>Whether above-the-fold images are prioritised correctly.</li>



<li>Whether below-the-fold images are lazy loaded.</li>



<li>Whether product images follow consistent aspect ratios.</li>



<li>Whether decorative imagery is being overused.</li>



<li>Whether app-injected images bypass the intended theme logic.</li>



<li>Whether alt text and filenames are meaningful.</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Shopify can optimise delivery, but it cannot fully compensate for poor source assets or poor theme implementation.</strong></p>
</blockquote>
</blockquote>



<h2 class="wp-block-heading">Image Optimisations Clients Should Complete Before Uploading to Shopify<br /></h2>



<h3 class="wp-block-heading">1. Resize images to a sensible maximum dimension</h3>



<p class="wp-block-paragraph">Clients should avoid uploading enormous camera-original files unless there is a specific need for zoom-level detail. If an image will never be displayed above 2000px wide, uploading a 6000px file creates unnecessary storage and processing overhead.</p>



<p class="wp-block-paragraph">As a practical rule:</p>



<ul class="wp-block-list">
<li><strong>Product images:</strong> usually export between <strong>1600px and 2400px wide</strong>, depending on zoom requirements.</li>



<li><strong>Collection tiles:</strong> often <strong>800px to 1600px wide</strong> is sufficient.</li>



<li><strong>Homepage hero images:</strong> typically <strong>2000px to 3000px wide</strong> for desktop, with separate mobile crops where possible.</li>



<li><strong>Logos and icons:</strong> use <strong>SVG</strong> where appropriate, rather than raster images.</li>
</ul>



<p class="wp-block-paragraph">Shopify’s <code>image_url</code> filter supports generated widths up to 5760px, but images cannot be enlarged beyond their original dimensions. That means clients should upload large enough files for legitimate use cases, but not unnecessarily huge originals.</p>



<h3 class="wp-block-heading">2. Use the right file format</h3>



<p class="wp-block-paragraph">Choose the source format based on image type:</p>



<ul class="wp-block-list">
<li><strong>JPEG/JPG</strong> — best for photography, lifestyle imagery and product shots without transparency.</li>



<li><strong>PNG</strong> — best for transparency, sharp UI graphics or cases where lossless quality is required.</li>



<li><strong>SVG</strong> — best for logos, icons and simple vector graphics.</li>



<li><strong>WebP</strong> — useful for already-optimised web-ready assets, though Shopify can also deliver modern formats through its CDN where supported.</li>
</ul>



<p class="wp-block-paragraph">For most photographic ecommerce imagery, a well-compressed JPEG source is still a sensible and widely compatible upload format, with Shopify then handling transformed delivery to modern formats where appropriate.</p>



<h3 class="wp-block-heading">3. Compress images before upload</h3>



<p class="wp-block-paragraph">Clients should export images using tools that balance quality and file size. The objective is not maximum compression; it is visually lossless compression. A product image should retain texture, colour and detail, but should not contain unnecessary metadata or excessive quality settings.</p>



<p class="wp-block-paragraph">Suggested export targets:</p>



<ul class="wp-block-list">
<li><strong>Product listing thumbnails:</strong> ideally under <strong>100–200KB</strong> where possible.</li>



<li><strong>Product detail images:</strong> often <strong>200–400KB</strong>, depending on complexity.</li>



<li><strong>Hero banners:</strong> ideally under <strong>400–700KB</strong>, with careful treatment for mobile.</li>
</ul>



<p class="wp-block-paragraph">These are not hard limits, particularly for luxury or detail-led brands, but they are useful targets.</p>



<h3 class="wp-block-heading">4. Create separate crops for mobile and desktop</h3>



<p class="wp-block-paragraph">A common mistake is to use a single wide desktop hero image for all devices. On mobile, this can result in either poor cropping or excessive asset size.</p>



<p class="wp-block-paragraph">Clients should provide:</p>



<ul class="wp-block-list">
<li>A desktop crop, usually landscape.</li>



<li>A mobile crop, often portrait or square.</li>



<li>A clearly defined focal point.</li>



<li>Safe zones for overlaid text, calls to action and navigation.</li>
</ul>



<p class="wp-block-paragraph">This is especially important for luxury campaign imagery, where the subject, model, product or composition may be carefully art-directed.</p>



<h3 class="wp-block-heading">5. Standardise product image aspect ratios</h3>



<p class="wp-block-paragraph">Inconsistent product image ratios can create layout shifts, uneven grids and awkward cropping. Clients should agree image rules before content population begins.</p>



<p class="wp-block-paragraph">For example:</p>



<ul class="wp-block-list">
<li>Fashion model photography: 4:5 or 3:4.</li>



<li>Product packshots: 1:1 or 4:5.</li>



<li>Lifestyle banners: 16:9 or 21:9.</li>



<li>Editorial tiles: controlled set of ratios, not arbitrary uploads.</li>
</ul>



<p class="wp-block-paragraph">Consistent image ratios help the theme reserve layout space and avoid visual instability.</p>



<h3 class="wp-block-heading">6. Remove unnecessary metadata</h3>



<p class="wp-block-paragraph">Camera metadata, location data, colour profiles and editing history can increase file size. Before uploading, clients should strip unnecessary metadata while preserving the correct colour profile for web display.</p>



<h3 class="wp-block-heading">7. Write useful filenames and alt text</h3>



<p class="wp-block-paragraph">Image optimisation is not only about compression. Filenames and alt text support accessibility, SEO and content management.</p>



<p class="wp-block-paragraph">Instead of:</p>



<pre class="wp-block-code"><code>IMG_9023-final-final-v4.jpg</code></pre>



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



<pre class="wp-block-code"><code>black-leather-crossbody-bag-front.jpg</code></pre>



<p class="wp-block-paragraph">Alt text should describe the image clearly and naturally, particularly for product images. Shopify’s image object includes an <code>alt</code> property, and Shopify recommends using <code>image_url</code> and <code>image_tag</code> filters to display images in storefront themes.</p>



<h3 class="wp-block-heading">8. Avoid uploading text-heavy images</h3>



<p class="wp-block-paragraph">Text embedded inside images is difficult to localise, poor for accessibility and can become blurry on small screens. Where possible, campaign text should be rendered as HTML over the image, not baked into the image itself.</p>



<p class="wp-block-paragraph">This gives the implementation team more control over:</p>



<ul class="wp-block-list">
<li>Responsive typography.</li>



<li>Accessibility.</li>



<li>Translation.</li>



<li>SEO.</li>



<li>A/B testing.</li>



<li>Mobile layout.</li>
</ul>



<h3 class="wp-block-heading">9. Check colour and quality after compression</h3>



<p class="wp-block-paragraph">Compression should be reviewed visually before upload, especially for luxury brands where material detail, colour accuracy and finish matter. Leather grain, jewellery reflections, fabric texture and cosmetics shades can all be affected by aggressive compression.</p>



<p class="wp-block-paragraph">Clients should compare:</p>



<ul class="wp-block-list">
<li>Original export.</li>



<li>Compressed version.</li>



<li>Mobile preview.</li>



<li>Product zoom state.</li>



<li>Dark and light background contexts.</li>
</ul>



<h2 class="wp-block-heading">Implementation Recommendations for Shopify Teams</h2>



<p class="wp-block-paragraph">Although this article focuses on client-side image preparation, the Shopify implementation also needs to be correct.</p>



<p class="wp-block-paragraph">Development teams should:</p>



<ul class="wp-block-list">
<li>Use <code>image_url</code> instead of hardcoded original image URLs.</li>



<li>Specify explicit width or height parameters, because Shopify requires one of these for <code>image_url</code>.</li>



<li>Use responsive <code>srcset</code> and <code>sizes</code>.</li>



<li>Lazy load below-the-fold images.</li>



<li>Avoid lazy loading the main above-the-fold hero/LCP image.</li>



<li>Reserve image dimensions to reduce layout shift.</li>



<li>Audit third-party apps that inject unoptimised media.</li>



<li>Test templates with Lighthouse, WebPageTest and real device throttling.</li>
</ul>



<p class="wp-block-paragraph">Shopify’s performance best-practice documentation makes it clear that performance is central to merchant success and influences conversion, repeat business and search visibility. Image handling should therefore be treated as part of the technical delivery strategy, not as an afterthought during content entry.</p>



<h2 class="wp-block-heading">Final Thoughts</h2>



<p class="wp-block-paragraph">Image-heavy brand experiences are now a core part of premium ecommerce. For Shopify merchants, this creates a balancing act: the store must feel rich, immersive and visually distinctive, but it must also remain fast, responsive and usable across devices.</p>



<p class="wp-block-paragraph">Shopify’s CDN provides a strong technical foundation through global delivery, automatic compression, dynamic transformations and modern format support. Its Liquid image tooling allows development teams to request appropriately sized image variants rather than serving oversized originals. But the best results come when clients, designers, content teams and developers all follow a joined-up image strategy.</p>



<p class="wp-block-paragraph"><strong>Make sure you upload high-quality but sensible source assets, let Shopify’s CDN do its job, and ensure the theme requests the right image for the right context.</strong></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/">Luxury Without Lag: How to Optimise Images on Shopify for Speed and Impact</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/luxury-without-lag-how-to-optimise-images-on-shopify-for-speed-and-impact/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Magento Support Deadlines</title>
		<link>https://www.douglasradburn.co.uk/magento-support-deadlines/</link>
					<comments>https://www.douglasradburn.co.uk/magento-support-deadlines/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Sat, 11 Apr 2020 07:55:22 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1874</guid>

					<description><![CDATA[<p>COMMERCE Release Release Date End of Software Support Enterprise Edition 1.9 July 2010 July 2012** Enterprise Edition 1.10 February 2011 February 2013** Enterprise Edition 1.11 August 2011 August 2013** Enterprise <a class="read-more" href="https://www.douglasradburn.co.uk/magento-support-deadlines/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/magento-support-deadlines/">Magento Support Deadlines</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">COMMERCE</p>



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th>Release</th><th>Release Date</th><th>End of Software Support</th></tr></thead><tbody><tr><td>Enterprise Edition 1.9</td><td>July 2010</td><td>July 2012**</td></tr><tr><td>Enterprise Edition 1.10</td><td>February 2011</td><td>February 2013**</td></tr><tr><td>Enterprise Edition 1.11</td><td>August 2011</td><td>August 2013**</td></tr><tr><td>Enterprise Edition 1.12</td><td>April 2012</td><td>April 2014**</td></tr><tr><td>Enterprise Edition 1.13</td><td>October 2013</td><td>June 2020*</td></tr><tr><td>Enterprise Edition 1.14</td><td>June 2014</td><td>June 2020*</td></tr><tr><td>Enterprise Edition 2.0</td><td>November 2015</td><td>March 2018</td></tr><tr><td>Enterprise Edition 2.1</td><td>June 2016</td><td>June 2019</td></tr><tr><td>Magento Commerce 2.2</td><td>September 2017</td><td>December 2019***</td></tr></tbody></table></figure>



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



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



<figure class="wp-block-table is-style-stripes"><table><thead><tr><th>Release</th><th>Release Date</th><th>Bug fix maintenance</th><th>Security maintenance*</th></tr></thead><tbody><tr><td>Community Edition 1.0</td><td>March 2008</td><td>March 2009</td><td>March 2010</td></tr><tr><td>Community Edition 1.1</td><td>July 2008</td><td>July 2009</td><td>July 2010</td></tr><tr><td>Community Edition 1.2</td><td>December 2008 </td><td>December 2009</td><td>December 2010</td></tr><tr><td>Community Edition 1.3</td><td>March 2009</td><td>March 2010</td><td>March 2011</td></tr><tr><td>Community Edition 1.4</td><td>February 2010</td><td>February 2011</td><td>February 2012</td></tr><tr><td>Community Edition 1.5</td><td>February 2011</td><td>February 2012</td><td>June 2020</td></tr><tr><td>Community Edition 1.6</td><td>August 2011</td><td>August 2012</td><td>June 2020</td></tr><tr><td>Community Edition 1.7</td><td>April 2012</td><td>April 2013</td><td>June 2020</td></tr><tr><td>Community Edition 1.8</td><td>September 2013</td><td>September 2014</td><td>June 2020</td></tr><tr><td>Community Edition 1.9</td><td>May 2014</td><td>May 2015</td><td>June 2020</td></tr><tr><td>Community Edition 2.0</td><td>November 2015</td><td>March 2018 </td><td>March 2018</td></tr><tr><td>Community Edition 2.1</td><td>June 2016</td><td>June 2019</td><td>June 2019</td></tr><tr><td>Open Source 2.2**</td><td>September 2017</td><td>September 2019 </td><td>September 2019</td></tr></tbody></table></figure>
<p>The post <a href="https://www.douglasradburn.co.uk/magento-support-deadlines/">Magento Support Deadlines</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/magento-support-deadlines/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to improve customer experience, and increase revenue by using cross selling effectively in ecommerce</title>
		<link>https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/</link>
					<comments>https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Fri, 05 Jan 2018 21:25:59 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1713</guid>

					<description><![CDATA[<p>I remember an episode of The Office (US) where Ryan asks Micheal whether it is more cost effective to keep an existing customer, or to get a new one. According <a class="read-more" href="https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/">How to improve customer experience, and increase revenue by using cross selling effectively in ecommerce</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I remember an episode of The Office (US) where Ryan asks Micheal whether it is more cost effective to keep an existing customer, or to get a new one. According to <a href="http://www.amazon.co.uk/Marketing-Metrics-Definitive-Measuring-Performance/dp/0137058292/">Marketing Metrics: The Definitive Guide to Measuring Marketing Performance</a>;</p>
<blockquote><p>The probability of selling to a new prospect is 5-20%. The probability of selling to an existing customer is 60-70%.</p></blockquote>
<h2>So, what tools are at our disposal?</h2>
<p>Upselling, and cross-selling are important tools that introduce customers to bigger, better and complementary products that you offer.</p>
<p>Offline retailers have been doing this for a very long time &#8211; the main drive for a sales team is to sell the customer a more expensive item, or sell them more items.</p>
<h2>So, what is the difference between upselling and cross-selling?</h2>
<p>When I started researching this post, it became apparent that cross-sell, upsell and related products are all used interchangably by both consumers and by online stores. I surveyed the office (now around 70 people) and found that this caused some confusion. So, let&#8217;s define them and find some good examples to show their worth.</p>
<h3>Upselling</h3>
<p>I think that Dell is a great example for showing upselling working well online.</p>
<p>Their configuration tool is a constant up-selling model. Dell &#8220;starts&#8221; you on a base model, and then allows you to customise from there, immediately.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-24663" src="https://www.branded3.com/uploads/2015/05/dell-upsell-2.png" alt="dell-upsell-2" width="996" height="806" /></p>
<p>This style of customisation is upselling in practise. Why have this model, when you can have one better, or one we recommend more highly? Dell are trying to essentially get you to spend more money on the item you&#8217;re looking to purchase.</p>
<p>As soon as you add an item to your cart, Dell also shows you what you get by default, and then tries to offer you something better.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-24662" src="https://www.branded3.com/uploads/2015/05/dell-upsell.png" alt="dell-upsell" width="743" height="588" /></p>
<p>As you can see from the screenshot, you&#8217;re already signed up for 1 year enhanced support. Why not get 2, 3 or 4 years?</p>
<p>Upselling often brings higher gains, and in turn, higher customer retention for certain products.</p>
<h3>Cross selling</h3>
<p>Once you&#8217;ve bought your new shiny computer &#8211; and you&#8217;ve picked a better processor &#8211; what are you going to need to go with it? This is where cross selling comes in.</p>
<p>Do you need a mouse? A monitor? A headset? A printer?</p>
<p>All of these are logical cross sells when purchasing a computer. Things you might need.</p>
<p>Amazon is a great example of this.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25224" src="https://www.branded3.com/uploads/2015/06/amazon3.png" alt="amazon3" width="614" height="102" /></p>
<p>However, usually, you&#8217;d want to be offering a specific product.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25223" src="https://www.branded3.com/uploads/2015/06/amazon2.png" alt="amazon2" width="921" height="127" /></p>
<h2>So, is Upselling and Cross-Selling Important?</h2>
<p>Upselling and cross selling can boost revenue by selling more to your customer. It&#8217;s important to note that this interaction improves your customer&#8217;s experience too. If they&#8217;re buying a new TV, you can make sure they&#8217;re buying the right one for them, and it&#8217;s also important to offer them the items that will let them get the most out of their new product.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25222" src="https://www.branded3.com/uploads/2015/06/amazon1.png" alt="amazon1" width="857" height="131" /></p>
<h2>Which is more effective?</h2>
<p>According to a report from <a href="https://econsultancy.com/blog/8632-up-selling-is-20-times-more-effective-than-cross-selling-online/">PredictiveIntent</a> in 2012, upselling leads to around a 4% increase in conversion, whereas cross selling only raised around 0.2%.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25242" src="https://www.branded3.com/uploads/2015/06/predictive-intent.png" alt="predictive-intent" width="475" height="286" /></p>
<p>The research however did state that it was aggregated across all their clients, and individual clients had increases in conversion of around 3% when cross selling was displayed in certain key areas &#8211; such as the checkout process.</p>
<p>It&#8217;s an important balance for an e-commerce store to ensure that a customer ultimately reaches the &#8220;checkout&#8221; phase as quickly as possible and completes a purchase; weighed against offering the customer &#8211; who is in a buying mindset &#8211; the ability to purchase more from you.</p>
<h2>Cross selling</h2>
<p>With the majority of ecommerce platforms, it&#8217;s a lot easier to get data based on what sells, rather than looking more indepth into what users visited initially, and what they ended up buying. If categorisation is good, you can probably get a fairly good read on this, but it will require more indepth profiling, and you&#8217;ll need to give up product page real estate to promote other products. Whilst this makes sense for the likes of Dell, for non-specialised businesses it makes less sense.</p>
<p>Ultimately, you&#8217;d like your customer to spend more money with you, but how you go about this has the potential to be too direct and jarring for the customer experience on your site.</p>
<p>There are a number of factors to increase conversions of you cross sells. A major attribute is value. Try not to offer the customer items that are over 20% the price of the product they&#8217;re looking at.</p>
<p>Customers are much more likely to purchase lower-priced add-ons to their purchase than additional big ticket items. This obviously helps if the main item is already expensive. The same goes for upsell items &#8211; you don&#8217;t want to try and upsell to an item that costs a lot more than the original.</p>
<h2>Where to use Upsells and Cross sells</h2>
<p>So, you have an ecommerce store, where should you position your upsells and cross sells?</p>
<h3>Upsells</h3>
<p>Upsells really need to be on a product page.</p>
<p>Apple show a very good example of this across their iPhone and iMac pages:</p>
<p><a href="https://www.branded3.com/uploads/2015/06/iphone.jpg"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25506" src="https://www.branded3.com/uploads/2015/06/iphone.jpg" alt="iphone" width="920" height="574" /></a></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25505" src="https://www.branded3.com/uploads/2015/06/imac.jpg" alt="imac" width="920" height="516" /></p>
<p>You&#8217;ll probably see a more regular example from Amazon, or from Interflora.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25508" src="https://www.branded3.com/uploads/2015/06/amazon-customers.jpg" alt="amazon-customers" width="920" height="278" /></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25510" src="https://www.branded3.com/uploads/2015/06/interflora.jpg" alt="interflora" width="920" height="649" /></p>
<h3>Cross Sells</h3>
<p>You&#8217;ll often also see cross sells on product pages, but also on pre-basket pages, and on basket pages themselves.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25509" src="https://www.branded3.com/uploads/2015/06/amazon-bought-together.jpg" alt="amazon-bought-together" width="920" height="208" /></p>
<p>Tesco show cross sells on the basket page, but they could be doing so much more here &#8211; like selling me bags for the vacuum cleaner I&#8217;ve added.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-25512" src="https://www.branded3.com/uploads/2015/06/tesco1.jpg" alt="tesco" width="889" height="1120" /></p>
<h2>Finding the balance</h2>
<p>Remember, there is a fine line between interupting the checkout journey and empowering a customer to make an additional purchase.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/">How to improve customer experience, and increase revenue by using cross selling effectively in ecommerce</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/improve-customer-experience-increase-revenue-using-cross-selling-effectively-ecommerce/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Magento &#8211; A go-live checklist</title>
		<link>https://www.douglasradburn.co.uk/magento-a-go-live-checklist/</link>
					<comments>https://www.douglasradburn.co.uk/magento-a-go-live-checklist/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Fri, 05 Jan 2018 21:23:34 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1715</guid>

					<description><![CDATA[<p>Whilst there are many tasks you need to undertake during the development of a new Magento site, there are also a lot of steps you need to go through when <a class="read-more" href="https://www.douglasradburn.co.uk/magento-a-go-live-checklist/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/magento-a-go-live-checklist/">Magento &#8211; A go-live checklist</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Whilst there are many tasks you need to undertake during the development of a new Magento site, there are also a lot of steps you need to go through when a site goes live. This is also the case if that website is replacing one that already exists. So, whilst it&#8217;s not a definite list, here are a number of things you should make sure are on your radar:</p>
<h2>Caching</h2>
<p>Most likely, your development and even staging versions of your site don&#8217;t have caching turned on. Turn it on; if nothing else, you at least need your config caching activated.</p>
<h2>Google page speed</h2>
<p>Use the <a href="https://developers.google.com/speed/pagespeed/insights/">Google page insights tool</a>. It&#8217;s probably worth going over this while you&#8217;re still on your staging site but obviously, some page speed issues are likely to be improved by your live server infrastructure. Make sure you check over your live site as soon as possible. Since 21st April 2015, Google have been more vigilant with websites&#8217; mobile-friendliness. Use the <a href="https://www.google.com/webmasters/tools/mobile-friendly/">Google mobile friendly test</a> in order to ensure that your Magento store is ready.</p>
<h2>301 redirects</h2>
<p>If you already have a site &#8211; whether or not it&#8217;s on Magento, you&#8217;re going to need a firm 301 redirection plan. Google is clever, but why not make it as easy as possible for them? Provide it with a list of pages that you&#8217;ve moved from one place to another. Be as specific as possible and avoid double hops as much as can be helped. This might mean that you want to implement them at server level instead of rewrites within Magento where possible through something like Apache or Nginx. Redirect maps were built for this; if you have a large number of redirects, use them.</p>
<h2>Update base URL</h2>
<p>If you&#8217;ve been working on a staging site, or you&#8217;re replacing an existing site from your development version, make sure you update the base URL.</p>
<h2>Maintenance.flag</h2>
<p>If you&#8217;re making changes to a site, this file is your friend. If you create this file (it doesn&#8217;t need any content) &#8211; then Magento will go into maintenance mode. It&#8217;s especially useful if you&#8217;ve got a lot of files to upload over FTP and you&#8217;re not deploying via GIT. Create the file and then deploy.</p>
<h2>Switching payment methods from staging/test to live &#8211; and test your checkout!</h2>
<p>A lot of things stop customers buying from your site. The most important step to get right is when a customer is all the way through your funnel and they&#8217;re trying to put in their payment details, and something doesn&#8217;t work. It&#8217;s not just going to call into question how reliable your site is, but also how secure your site is. Additionally, you don&#8217;t want customers checking out with a test integration and expecting you to ship your items!</p>
<h2>Contact details and transactional emails</h2>
<p>The most important things for your customer before and after purchasing goods from your store are a) being able to contact you and b) knowing that they&#8217;ve made a purchase from a reputable company.</p>
<p>Make sure you have contact details on your site. Magento makes this easy &#8211; find them in System -&gt; Configuration -&gt; General, System -&gt; Configuration -&gt; Store Email Addresses, and System -&gt; Configuration -&gt; General -&gt; Store Information. Obviously, your template needs to show these details on the front end of the site &#8211; make sure you update them!</p>
<p>Transactional emails use a different logo to your main site. Ensure you have a &#8220;logo_email.gif&#8221; image in your theme folder if you haven&#8217;t already customised your email templates. When thinking about Magento, the sales order email is probably the only one that you can guarantee someone who purchases from your site. Make sure you test it &#8211; and that you&#8217;re happy with it. Make sure it&#8217;s packed full of information about the order and that you also take this opportunity to engage your customer as much as possible &#8211; highlighting promotions or upselling items, for example. Finally, ensure you test your emails with a testing system, like <a href="http://www.emailonacid.com">Emailonacid</a>.</p>
<h2>Newsletter subscription emails</h2>
<p>Turn them off if you haven&#8217;t styled them. By default, they&#8217;re a one-line plain text email and you don&#8217;t want to spam your customers, do you? It&#8217;s best to check whether you can subscribe and unsubscribe at the same time &#8211; you don&#8217;t want your customers testing this for you.</p>
<h2>Placeholder images</h2>
<p>You should have already ensured that all images have been uploaded for products in your store. You should be able to quickly run through the admin products grid to see that images have been uploaded, but creating a small script to double-check could be a good idea.</p>
<p>If you&#8217;ve missed any products, Magento has got your back. Placeholder images: where can you manage these? If you&#8217;re a developer, you could just create catalog/products/placeholder/small_image.jpg, catalog/products/placeholder/thumbnail.jpg and catalog/products/placeholder/image.jpg. If you want to help your merchant, you&#8217;re probably just going to want to take the easy option. Upload them in the admin system: System -&gt; Configuration -&gt; Catalog -&gt; Catalog -&gt; Product Image Placeholders. Here, you can set them for specific store views too.</p>
<h2>Test your orders, customers and products</h2>
<p>Don&#8217;t develop blindly; test everything. If you&#8217;re looking to make sure your site is ready for release, you&#8217;ll have added customer accounts, test orders and should have a large amount of products. Quite often, you might not have the finalised versions of any of these three.</p>
<p>To this end, make sure that you clear your database of all the test items you have before you go live. You don&#8217;t want to have to be trawling through lists of test orders with skewed reporting figures. You also don&#8217;t want to be sending automated emails to your test users or your customers buying test products. Furthermore, you don&#8217;t want Google crawling those products and listing them for the world to see.</p>
<h2>Your 404 page</h2>
<p>Make sure you have your 404 page customised for your store. Whilst the default 404 gives a nice &#8220;standard&#8221; interface, don&#8217;t go live with the default 404 page. It&#8217;s often overlooked and should be tailored to the experience your customers expect from your site. Be sure that as well as being in the correct design format, it provides relevant links to your customer journey and supports your site and brand.</p>
<h2>Log cleaning</h2>
<p>Another thing that you don&#8217;t worry too much about in development or staging environments are your logs. However, once live, this could bring your entire site down if said logs get too large. Make sure you&#8217;re checking them in development to make sure that you catch anything that is likely to cause you issues when you go live. Once live, ensure you set up log rotation so that you don&#8217;t have to sift through logs in order to find the item that is causing you a problem. System -&gt; Configuration -&gt; Advanced -&gt; System -&gt; Log Cleaning is your friend. This is disabled by default, meaning your database can essentially grow continuously. For high traffic stores, you might even want to disable the logging module entirely.</p>
<h2>SSL certificate</h2>
<p>If you&#8217;re making use of an SSL certificate, make sure it is in place. If possible, purchase a certificate for your staging environment so that you can test and make sure that all assets are available over a secure connection when required. Configure Magento to use this as the secure base URL as well. Some payment providers won&#8217;t allow you to go live until an SSL certificate is in place either.</p>
<h2>Module licenses</h2>
<p>Many modules require live and staging license keys. Make sure that you switch out the staging version for the live version when you go live. Otherwise, you might not have any shipping options, or perhaps your customers won&#8217;t be able to enter their card details! Make sure the keys are valid for your live domain when you make the switch.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/magento-a-go-live-checklist/">Magento &#8211; A go-live checklist</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/magento-a-go-live-checklist/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Image management for Magento1</title>
		<link>https://www.douglasradburn.co.uk/image-management-magento1/</link>
					<comments>https://www.douglasradburn.co.uk/image-management-magento1/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Fri, 05 Jan 2018 21:18:09 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1721</guid>

					<description><![CDATA[<p>From time to time, you might want to remove all the images that are associated with a Magento product. This might be for any number of reasons, but starting from <a class="read-more" href="https://www.douglasradburn.co.uk/image-management-magento1/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/image-management-magento1/">Image management for Magento1</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>From time to time, you might want to remove all the images that are associated with a Magento product.  This might be for any number of reasons, but starting from a blank slate is often a good plan.</p>
<p>Here, we&#8217;ll have a look at a script that will remove all images from a product, and then add some new ones that we&#8217;ve got ready.  We&#8217;re working on the assumption here that the client has had a number of new shots done for all products, so we&#8217;ll be running through all products in the system.  We&#8217;re going to also assume that there is one image per product to be added and that it is named the same as the SKU of the product.</p>
<h2>Removing product images</h2>
<p>Firstly, we&#8217;ll grab all products in the system.</p>
<pre lang="PHP">
$collection = Mage::getModel('catalog/product')->getCollection();
</pre>
<p>If you have a lot of products in your store, you might want to filter this, so you&#8217;re only dealing with a limited, and managable product range.</p>
<p>To remove images from a product, the easiest way is via the Product Attribute Media API Model.   So, we&#8217;re going to grab that model now.</p>
<pre lang="PHP">
$mediaApi = Mage::getModel("catalog/product_attribute_media_api");
</pre>
<p>This model has a <code>remove</code> method which takes two variables as input.  The product id, and the file you&#8217;re trying to remove (as remember, a product might have multiple images).  We&#8217;re removing all images in our example, so we&#8217;re going to retrieve all images for the product and then loop through them removing each one, one at a time.  If we wanted to only remove a particular image, or image tagged specifically (eg. the base image, swatch small images or thumbnail), then we could check within this loop.</p>
<pre lang="PHP">
$items = $mediaApi->items($product->entity_id);
</pre>
<p><code>$items</code> will be an array of media attached to the product &#8211; meaning we can iterate through it and get each item. <code>$item['file']</code> will be the filename of the asset, so we can call the <code>catalog/product_attribute_media_api</code> model&#8217;s remove method.</p>
<p>Here&#8217;s the code in full.</p>
<pre lang="PHP">
error_reporting(E_ALL);
ini_set('memory_limit', '1024M');
require_once '../app/Mage.php';

umask(0);
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);

// load all products
$collection = Mage::getModel('catalog/product')->getCollection();

$mediaApi = Mage::getModel("catalog/product_attribute_media_api");

foreach ($collection as $product) {
    echo $product->entity_id;

    try {
        $items = $mediaApi->items($product->entity_id);
	foreach($items as $item) {
	    if($mediaApi->remove($product->entity_id, $item['file'])){
                echo " image removed";
            } else {
                echo $item['file'] . " image not removed";
            }
	}
    } catch (Exception $exception){
        echo $exception->getMessage();
        die();
    }

    echo "\n";

}
</pre>
<p>I&#8217;ve added a quick bit of debugging in so that we can see line-by-line, product-by-product if the code is passing or failing.  If you&#8217;d like to live in a world of make-believe and wonder, simply comment out or remove the &#8220;echo&#8221; statements.</p>
<h2>Adding Images</h2>
<p>Now that we&#8217;ve removed all the images, we&#8217;re going to add some new ones.</p>
<h3>Preparing image files</h3>
<p>Magento needs the images to be in a specific folder in order to import media into the system when you&#8217;re using dataflow or product import services within the admin system &#8211; namely <code>/media/import/</code> When adding images programmatically, you don&#8217;t need to do this, but I&#8217;m going to follow best practice in my example and make other developer&#8217;s lives easier rather than having random folders of images laying around &#8211; so, I&#8217;m going to store them in here anyway.</p>
<h3>Importing the images</h3>
<p>As mentioned previously, we&#8217;re going to make some assumptions in our script:</p>
<p>&#8211; All new images to import is in /media/import/new-product-images-2014/<br />
&#8211; There is only a single image per product<br />
&#8211; The image has the same name as the SKU of the product<br />
&#8211; The image should be set as the main image, small image &#038; thumbnail for the product</p>
<p>Given the above information, there is actually only one key bit of information we need to add the product image into Magento.  Once we have a <code>$product</code> object, we can call the <code>addImageToMediaGallery()</code> method on it to add an image from a path on the filesystem.</p>
<p>The <code>addImageToMediaGallery()</code> method takes 4 attributes.</p>
<ul>
<li>$file &#8211; The file path of the image in the filesystem</li>
<li>$mediaAttribute &#8211; Array of where the image should appear</li>
<li>$move &#8211; Should the image be moved (if true) or copied</li>
<li>$exclude &#8211; Mark the image as disabled in the product page view</li>
</ul>
<pre lang="PHP">
$_productImage = "media/import/new-product-images-2014/mysku.jpg"; // our reference to the new image
$_product->addImageToMediaGallery($_productImage, array( 'image', 'small_image', 'thumbnail' ), false, false);
</pre>
<p>The post <a href="https://www.douglasradburn.co.uk/image-management-magento1/">Image management for Magento1</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/image-management-magento1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How Magento2 implements Plugins</title>
		<link>https://www.douglasradburn.co.uk/magento2-implements-plugins/</link>
					<comments>https://www.douglasradburn.co.uk/magento2-implements-plugins/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Thu, 23 Feb 2017 21:09:28 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1783</guid>

					<description><![CDATA[<p>I recently wrote a post about Aspect Oriented Programming, and how developers can use it within Magento 2. However, I didn&#8217;t cover anything about what Magento 2 is actually doing <a class="read-more" href="https://www.douglasradburn.co.uk/magento2-implements-plugins/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/magento2-implements-plugins/">How Magento2 implements Plugins</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I recently wrote a post about <a href="http://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/">Aspect Oriented Programming</a>, and how developers can use it within Magento 2.  However, I didn&#8217;t cover anything about what Magento 2 is actually doing in the background. How exactly is it providing the Plugin?</p>
<p>Interceptors are how Magento 2&#8217;s object system implements the plugin system.</p>
<p>In theory, you don&#8217;t need to worry about how Magento provides the underlying plugin system in order to use it.</p>
<p>When you ask Magento for an object, it may return an interceptor, or it might return your referenced class.</p>
<h2>How it&#8217;s implemented</h2>
<p>If a class has a plugin configured, the object manager will return an interceptor.</p>
<p>Magento handles the delivery of an interceptor different ways depending on how your environment is configured.</p>
<h3>Production Mode</h3>
<p>In production mode, Magento will look through your solution during compilation, and create interceptors for plugins where required.</p>
<p>Magento handles plugin creation with a separate PHP autoloader. If a developer triggers a PHP autoload event, and the composer based autoloader can&#8217;t find the class file, a second registered autoloader is called:</p>
<p><code>Magento\Framework\Code\Generator\Autoloader</code></p>
<pre lang="php">
/**
 * Load specified class name and generate it if necessary
 *
 * @param string $className
 * @return bool True if class was loaded
 */
public function load($className)
{
    if (!class_exists($className)) {
        return Generator::GENERATION_ERROR != $this->_generator->generateClass($className);
    }
    return true;
}
</pre>
<h3>So, what&#8217;s happening here?</h3>
<p>Magento is making a call to <code>Magento\Framework\Code\Generator::generateClass</code>.</p>
<pre lang="php">
/**
 * Generate Class
 *
 * @param string $className
 * @return string | void
 * @throws \RuntimeException
 * @throws \InvalidArgumentException
 */
public function generateClass($className)
{
    $resultEntityType = null;
    $sourceClassName = null;
    foreach ($this->_generatedEntities as $entityType => $generatorClass) {
        $entitySuffix = ucfirst($entityType);
        // If $className string ends with $entitySuffix substring
        if (strrpos($className, $entitySuffix) === strlen($className) - strlen($entitySuffix)) {
            $resultEntityType = $entityType;
            $sourceClassName = rtrim(
                substr($className, 0, -1 * strlen($entitySuffix)),
                '\\'
            );
            break;
        }
    }

    if ($skipReason = $this->shouldSkipGeneration($resultEntityType, $sourceClassName, $className)) {
        return $skipReason;
    }

    $generatorClass = $this->_generatedEntities[$resultEntityType];
    /** @var EntityAbstract $generator */
    $generator = $this->createGeneratorInstance($generatorClass, $sourceClassName, $className);
    if ($generator !== null) {
        $this->tryToLoadSourceClass($className, $generator);
        if (!($file = $generator->generate())) {
            $errors = $generator->getErrors();
            throw new \RuntimeException(implode(' ', $errors));
        }
        if (!$this->definedClasses->isClassLoadableFromMemory($className)) {
            $this->_ioObject->includeFile($file);
        }
        return self::GENERATION_SUCCESS;
    }
}
</pre>
<p>As you can see from the code above, after name matching, it checks to see if there is any reason that the generation should be skipped.</p>
<h4>Let&#8217;s take a look at an example.</h4>
<p>We&#8217;re going to take a look at <code>vendor/magento/module-customer/Model/Plugin/AllowedCountries.php</code>. As you can see, we have a before method called on <code>getAllowedCountries</code>.</p>
<pre lang="php">
namespace Magento\Customer\Model\Plugin;

use Magento\Customer\Model\Config\Share;
use Magento\Store\Api\Data\WebsiteInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Class AllowedCountries
 */
class AllowedCountries
{
    /**
     * @var \Magento\Customer\Model\Config\Share
     */
    private $shareConfig;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @param Share $share
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        Share $share,
        StoreManagerInterface $storeManager
    ) {
        $this->shareConfig = $share;
        $this->storeManager = $storeManager;
    }

    /**
     * Retrieve all allowed countries or specific by scope depends on customer share setting
     *
     * @param \Magento\Directory\Model\AllowedCountries $subject
     * @param string | null $filter
     * @param string $scope
     * @return array
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function beforeGetAllowedCountries(
        \Magento\Directory\Model\AllowedCountries $subject,
        $scope = ScopeInterface::SCOPE_WEBSITE,
        $scopeCode = null
    ) {
        if ($this->shareConfig->isGlobalScope()) {
            //Check if we have shared accounts - than merge all website allowed countries
            $scopeCode = array_map(function (WebsiteInterface $website) {
                return $website->getId();
            }, $this->storeManager->getWebsites());
            $scope = ScopeInterface::SCOPE_WEBSITES;
        }

        return [$scope, $scopeCode];
    }
}
</pre>
<p>Magento creates an interceptor, thus:</p>
<pre lang="php">
namespace Magento\Directory\Model\AllowedCountries;

/**
 * Interceptor class for @see \Magento\Directory\Model\AllowedCountries
 */
class Interceptor extends \Magento\Directory\Model\AllowedCountries implements \Magento\Framework\Interception\InterceptorInterface
{
    use \Magento\Framework\Interception\Interceptor;

    public function __construct(\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Store\Model\StoreManagerInterface $storeManager)
    {
        $this->___init();
        parent::__construct($scopeConfig, $storeManager);
    }

    /**
     * {@inheritdoc}
     */
    public function getAllowedCountries($scope = 'website', $scopeCode = null)
    {
        $pluginInfo = $this->pluginList->getNext($this->subjectType, 'getAllowedCountries');
        if (!$pluginInfo) {
            return parent::getAllowedCountries($scope, $scopeCode);
        } else {
            return $this->___callPlugins('getAllowedCountries', func_get_args(), $pluginInfo);
        }
    }
</pre>
<p>Magento is extending the original class to allow our additional code to run.  It returns the Interceptor class rather than the original.</p>
<p>This explains a few of the limitations listed in the previous article, such as why you cannot use a Plugin on a class with a final method, or on static method calls. </p>
<h3>Developer Mode</h3>
<p>In developer mode, where everything is done on the fly, the following code is run:</p>
<pre lang="php">
public function getInstanceType($instanceName)
{
    $type = parent::getInstanceType($instanceName);
    if ($this->interceptionConfig && $this->interceptionConfig->hasPlugins($instanceName)
        && $this->interceptableValidator->validate($instanceName)
    ) {
        return $type . '\\Interceptor';
    }
    return $type;
}
</pre>
<p>From <code>vendor/magento/framework/Interception/ObjectManager/Config/Developer.php</code></p>
<p>Magento internally uses the <code>vendor/magento/framework/Interception/ObjectManager/InterceptableValidator</code> class in order to validate an interceptor.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/magento2-implements-plugins/">How Magento2 implements Plugins</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/magento2-implements-plugins/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Aspect Oriented Programming (AOP) &#8211; Plugins with Magento2</title>
		<link>https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/</link>
					<comments>https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Mon, 06 Feb 2017 22:01:49 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[General]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1765</guid>

					<description><![CDATA[<p>A little about AOP Aspect Oriented Programming (AOP) is a programming paradigm that tries to increase modularity of code. It does this by allowing the separation of &#8220;cross-cutting&#8221; concerns. This <a class="read-more" href="https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/">Aspect Oriented Programming (AOP) &#8211; Plugins with Magento2</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>A little about AOP</h2>
<p>Aspect Oriented Programming (AOP) is a programming paradigm that tries to increase modularity of code.  It does this by allowing the separation of &#8220;cross-cutting&#8221; concerns.</p>
<p>This means that the code is streamlined to do exactly what it needs to do, without having to worry about code that operates to undertake processing throughout the rest of the application.</p>
<h3>An example</h3>
<p>Let&#8217;s have a look at a quick example.  This example focuses on the need to log within an application.</p>
<pre lang="php">
public function doSomething()
{
    try {
        //do something
        $this->logger->debug("something");
    } catch (\Exception $e) {
        $this->logger->critical($e);
    }
}
</pre>
<p>As you can see from the example, we have a large amount of code that is taking care of logging, and logging any exceptions, but the main code is relatively small.  If we wanted to swap out our logging pattern, it could be quite a large undertaking, as you can imagine that this kind of code would be littered everywhere within our application.</p>
<p>Wouldn&#8217;t it be much nicer, if our code just did what it needed to do.</p>
<pre lang="php">
public function doSomething()
{
    //do something
}
</pre>
<h3>How does AOP work?</h3>
<p>AOP tries to overcome this by adding additional behaviour to our existing code &#8211; an &#8220;advice&#8221; &#8211; without modifying the code itself.  Instead, it specifies which code is modified via a &#8220;point cut&#8221; specification.</p>
<p>AOP allows developers to apply these &#8220;advice&#8221; to crosscutting concerns &#8211; logic applicable throughout the application, that affects the entire application &#8211; such as logging in our example above. Security and data transfer are also concerns which are needed in almost every module of an application.</p>
<h2>A segway into SOLID</h2>
<p>Let&#8217;s have a quick segway into SOLID principles.  These are five core principles of object oriented programming and design.  The concept behind these principles is that it will allow for greater maintainable code that can be extended over time.</p>
<p>The principles are:</p>
<ul>
<li>Single responsibility</li>
<li>Open-closed</li>
<li>Liskov substitution</li>
<li>Interface segregation</li>
<li>Dependency inversion</li>
</ul>
<p>What we&#8217;re looking at is the <strong>Single responsibility</strong>.  We want the code to take care of it&#8217;s concerns/responsibilities only.  Classes and methods should only have one responsibility, and do one thing.</p>
<p>AOP allows us to satisfy this requirement.  Our core classes and methods do the task that we expect, and the cross cutting concerns are taken care of within code that interacts with our existing code.</p>
<h2>Plugins in Magento2</h2>
<p>Magento2&#8217;s implementation of AOP is done in the form of Plugins &#8211; you may also see these referred to as interceptors in Magento documentation.</p>
<p>Plugins allow a developer to &#8220;Listen&#8221; to any public method call made on an object manager controlled object and take programmatic action.  It allows for the behaviour of the method to be modified and have code run before, after, or around the method call.</p>
<p>This means you could extend, or even substitute an existing method.</p>
<p>Additionally, you can change the return value of any method call made on an object manager controlled object, change the arguments of any method call made to an object manager controlled object, all whilst other plugins are doing the same thing to the same method in a predictable way.</p>
<p>Magento implements this using the <a href="https://en.wikipedia.org/wiki/Interceptor_pattern" target="_blank">interceptor pattern</a> (why you might see these referred to as interceptors).</p>
<p><img loading="lazy" decoding="async" src="http://www.douglasradburn.co.uk/wp-content/uploads/Interceptor.VSD_Example.png" alt="" width="481" height="97" class="aligncenter size-full wp-image-1772" /></p>
<p>As mentioned in the wikipedia article referenced above, one of the key aspects of the interceptor pattern is that <i>the rest of the system does not have to know something has been added or changed and can keep working as before</i>.</p>
<h3>Limitations to the implementation</h3>
<p>Magento&#8217;s implementation means that you cannot use AOP with any of the following:</p>
<ul>
<li>Objects that are instantiated before Magento\Framework\Interception is bootstrapped</li>
<li>Final methods</li>
<li>Final classes</li>
<li>Any class that contains at least one final public method</li>
<li>Non-public methods</li>
<li>Class methods (such as static methods)</li>
<li>__construct</li>
<li>Virtual types</li>
</ul>
<h2>Our AOP options</h2>
<p><strong>Before:</strong> Executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).</p>
<p><em>In Magento2, we use the method prefix <code>before</code> and simply camel case the method name.</em></p>
<p><strong>After:</strong> Executed after a join point completes normally: for example, if a method returns without throwing an exception.</p>
<p><em>In Magento2, we use the method prefix <code>after</code> and simply camel case the method name.</em></p>
<p><strong>Around:</strong> Surrounds a join point such as a method invocation.</p>
<p>Around can perform custom behaviour before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception. </p>
<p><em>In Magento2, we use the method prefix <code>around</code> and simply camel case the method name.</em></p>
<h2>Defining Plugins in Magento2</h2>
<p>Magento2 allows developers to create plugins using standard PHP classes, and reference them using XML within <code>di.xml</code>.</p>
<p>There are a series of <em>Required options</em>:</p>
<ul>
<li><strong>Type name:</strong> The name of a class or interface that needs to be followed.</li>
<li><strong>Plugin name:</strong> The name for the new plugin in Magento 2.  This is arbitrary and is simply used during mergin XML to give your plugin a unique reference.
</li>
<li><strong>Plugin type:</strong> The name of a plugin&#8217;s class or its virtual type. <code>\Vendor\Module\Plugin\ModelName\Plugin</code>.</li>
</ul>
<p>There are additionally couple of optional options:</p>
<ul>
<li><strong>Plugin sortOrder:</strong> Set the order in which the plugin should be called.</li>
<li><strong>Plugin disabled:</strong> That allows you enable or disable a plugin quickly. As the default configuration, the chosen value is false.  This allows you to write a global <code>di.xml</code> and then override (and disable) a Plugin in an area specific <code>di.xml</code> such as frontend / adminhtml.</li>
</ul>
<h2>Some examples from core</h2>
<h3>Before</h3>
<p>Here we have an example from <code>\Magento\Theme\Model\Theme\Plugin\Registration</code>.</p>
<p>Note the keyword <code>before</code> in front of the method we want to intercept <code>dispatch</code>.</p>
<pre lang="php">
/**
 * Add new theme from filesystem and update existing
 *
 * @param AbstractAction $subject
 * @param RequestInterface $request
 *
 * @return void
 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 */
public function beforeDispatch(
    AbstractAction $subject,
    RequestInterface $request
) {
    try {
        if ($this->appState->getMode() != AppState::MODE_PRODUCTION) {
            $this->themeRegistration->register();
            $this->updateThemeData();
        }
    } catch (LocalizedException $e) {
        $this->logger->critical($e);
    }
}
</pre>
<p>It&#8217;s achieved with the following entry in <code>di.xml</code> specifically in the adminhtml scope.</p>
<pre lang="xml">
<type name="Magento\Backend\App\AbstractAction">
    <plugin name="themeRegistrationFromFilesystem" type="Magento\Theme\Model\Theme\Plugin\Registration"></plugin>
</type>
</pre>
<h3>After</h3>
<p>This function specifically invalidates the indexer registry when an item is deleted.</p>
<pre lang="php">
/**
 * Invalidate design config grid indexer on store removal
 *
 * @param StoreStore $subject
 * @param StoreStore $result
 * @return StoreStore
 *
 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 */
public function afterDelete(StoreStore $subject, $result)
{
    $this->indexerRegistry->get(Config::DESIGN_CONFIG_GRID_INDEXER_ID)->invalidate();
    return $result;
}
</pre>
<p>It&#8217;s achieved with the following entry in <code>di.xml</code>.</p>
<pre lang="xml">
<type name="Magento\Store\Model\Group">
    <plugin name="themeDesignConfigGridIndexerStoreGroup" type="Magento\Theme\Model\Indexer\Design\Config\Plugin\StoreGroup"></plugin>
</type>
</pre>
<h3>Around</h3>
<p>Here we have an example from <code>\Magento\Theme\Model\Url\Plugin\Signature</code>.</p>
<p>This functionality ensures that any call to <code>getBaseUrl()</code> returns a string that ends with a forward slash.  Note the keyword <code>around</code> in front of the method we want to intercept <code>getBaseUrl</code>.</p>
<pre lang="php">
/**
 * Append signature to rendered base URL for static view files
 *
 * @param \Magento\Framework\Url\ScopeInterface $subject
 * @param callable $proceed
 * @param string $type
 * @param null $secure
 * @return string
 * @see \Magento\Framework\Url\ScopeInterface::getBaseUrl()
 *
 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 */
public function aroundGetBaseUrl(
    \Magento\Framework\Url\ScopeInterface $subject,
    \Closure $proceed,
    $type = \Magento\Framework\UrlInterface::URL_TYPE_LINK,
    $secure = null
) {
    $baseUrl = $proceed($type, $secure);
    if ($type == \Magento\Framework\UrlInterface::URL_TYPE_STATIC && $this->isUrlSignatureEnabled()) {
        $baseUrl .= $this->renderUrlSignature() . '/';
    }
    return $baseUrl;
}
</pre>
<p>It&#8217;s achieved with the following entry in <code>di.xml</code></p>
<pre lang="xml">
<type name="Magento\Framework\Url\ScopeInterface">
    <plugin name="urlSignature" type="Magento\Theme\Model\Url\Plugin\Signature"></plugin>
</type>
</pre>
<h3>Using around</h3>
<p>You&#8217;ll notice in the method above that there is a call to <code>$proceed</code>, a PHP closure that allows the method to call the method it is intercepting.  With great power, comes great responsibility.  It is the around method&#8217;s responsibility to call the callable.  If it doesn&#8217;t, it prevents the execution of all the plugins that follow it.</p>
<p>When you use around on a method that accepts arguments, your plugin method must also accept those arguments &#8211; and you must forward them when you invoke the proceed callable. You must match the original signature of the method, including default parameters and type hints &#8211; otherwise, your plugin may introduce unintended exceptions.</p>
<p>If you&#8217;re doing nothing with these arguments, you can use <a href="https://wiki.php.net/rfc/variadics" target="_blank">variadics</a> and <a href="https://wiki.php.net/rfc/argument_unpacking" target="_blank">argument unpacking</a> to achieve this.</p>
<p>This doesn&#8217;t seem to be used in CORE at the moment, but allows you to do the following:</p>
<pre lang="php">
    public function aroundSave(\X\Y\Model\Z $subject, callable $proceed, ...$args)
</pre>
<h2>Prioritisation</h2>
<p>I&#8217;d suggest checking out the dev docs for more details on prioritisation, as they&#8217;re got some great examples: <a href="http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html#prioritizing-plugins" target="_blank">Prioritizing plugins</a>.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/">Aspect Oriented Programming (AOP) &#8211; Plugins with Magento2</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/aspect-oriented-programming-aop-plugins-magento2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MageTitansMCR 2016</title>
		<link>https://www.douglasradburn.co.uk/magetitansmcr-2016/</link>
					<comments>https://www.douglasradburn.co.uk/magetitansmcr-2016/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Sun, 13 Nov 2016 20:43:32 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1752</guid>

					<description><![CDATA[<p>As another MageTitans ends, I&#8217;ve come away super charged as I always do from these events. I always try to make sure that I&#8217;m surrounded by intellegent people, and these <a class="read-more" href="https://www.douglasradburn.co.uk/magetitansmcr-2016/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/magetitansmcr-2016/">MageTitansMCR 2016</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>As another MageTitans ends, I&#8217;ve come away super charged as I always do from these events. I always try to make sure that I&#8217;m surrounded by intellegent people, and these types of events means that is certainly the case &#8211; in the most positive of ways!</p>
<p>For anyone not in the know, MageTitans is an extremely successful Magento conference organised by Manchester Digital &#038; Space48 for the last two years &#8211; with additional events around the world.</p>
<p>MageTitans is an event for Magento Developers, curated by Magento developers. </p>
<p>I don&#8217;t want to go too in-depth breaking down each talk – as I&#8217;m sure others will, and the videos will be available soon, but I did want to highlight some of the key concepts and talking points from the conference presentations.</p>
<ul>
<li>All Magento2 online training resources will be free in the months of December and January</li>
<li>In the future, there will be longer pre-notification of patches for both Magento 1 and Magento 2</li>
<li>Frontend framework for Magento 2 is moving toward BEM &#038; SASS</li>
<li>Use the &#8220;best practise&#8221; tag on Stack Exchange to find (and explain) best practise for Magento 2</li>
<li>Don&#8217;t use the Object Manager in Magento 2 directly!</li>
<li>Avoid Gold-Plating your new extensions &#8211; Support a version of Magento 2, and avoid spending hundreds of hours supporting older versions of Magento 2 &#8211; people will upgrade</li>
<li>No one actually knows what OOP is &#8211; thanks to Vinai for filling us in!</li>
<li>Alan Kay got a lot of mentions &#8211; the father of OOP</li>
<li>We can all learn from functional programming even if we&#8217;re not using it</li>
<li>Monitor everything you can about your application</li>
<li>Agree on metrics with your client, and measure performance</li>
<li>Don&#8217;t make decisions on (not) caching within your community extension.  Allow store owners to take control.</li>
<li>There is a great New Relic dashboard for looking at FPC hits/misses &#8211; <a href="https://maxchadwick.xyz/blog/monitoring-magento-fpc-hit-rate">https://maxchadwick.xyz/blog/monitoring-magento-fpc-hit-rate</a></li>
<li>The devdocs now advise against using $isScopePrivate</li>
<li>Always think about FPC at the beginning of every project.  Could toward it</li>
<li>Skip creating sessions for bots, crawlers and anything else that doesn&#8217;t need them with <a href="https://github.com/AOEpeople/Aoe_BlackHoleSession">https://github.com/AOEpeople/Aoe_BlackHoleSession</a></li>
<li>Magento will never be cutting-edge on the frontend, but it doesn&#8217;t need to be.  There is a pay-off for having reliability.</li>
<li>Mini cart, basket and dashboard are good core examples for AJAX calls for private data</li>
<li>There is still a long way to go before Magento2 can support <a href="https://www.w3.org/TR/SRI/">Subresource Integrity</a></li>
<li>JS has been radically overhauled in Magento 2.  Magento 1 had 386 JS files, Magento 2 has over 12k</li>
<li>Magento 2.1.3 has optimised rendering flow for tabs &#8211; hidden tabs are initialised, but not rendered</li>
<li>Always start your AWS EC2 instances in auto scaling groups</li>
<li>Get involved with <a href="http://www.meet-magento.world">Meet Magento World</a></li>
<li>Don&#8217;t forget to rate the speakers &#8211; <a href="https://joind.in/event/mage-titans-mcr/schedule/list">https://joind.in/event/mage-titans-mcr/schedule/list</a></li>
</ul>
<p>The post <a href="https://www.douglasradburn.co.uk/magetitansmcr-2016/">MageTitansMCR 2016</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/magetitansmcr-2016/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Setting up a Magento Users Group &#8211; Why finding like-minded people is so important</title>
		<link>https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/</link>
					<comments>https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/#respond</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Fri, 17 Jun 2016 20:02:34 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Magento]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1735</guid>

					<description><![CDATA[<p>At MageTitans Mini this past May, I did an impromptu talk entitled &#8220;Setting up a Magento User Group&#8221;. Whilst most of that was me trying to remember why I&#8217;d even <a class="read-more" href="https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/">Setting up a Magento Users Group &#8211; Why finding like-minded people is so important</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>At <a href="http://www.magetitans.co.uk">MageTitans Mini</a> this past May, I did an impromptu talk entitled &#8220;Setting up a Magento User Group&#8221;.</p>
<p>Whilst most of that was me trying to remember why I&#8217;d even thought getting on stage was a good idea, I wanted to get down some thoughts in a post to share.</p>
<p>For the best part, I don&#8217;t have to remind people how great the Magento community is.  You&#8217;re probably already part of it if you&#8217;re reading this.  This great community comes together at events like Mage Titans, and other events, all across the globe.  However, these events are spread out over a large geographical area, and unless you have a very rich boss or you&#8217;re Ben Marks, chances are you might get to visit one every 6 months.</p>
<p>This is where a local user group can play a key role.</p>
<p>As Cal Evans was once quoted: &#8220;Community Works! If I have a question, someone has the answer. If I have a problem, someone has the solution. If I just need to vent, there’s always someone out there willing to be my rubber duck.&#8221;</p>
<p>Usergroups are fantastic places to meet like minded people.  What&#8217;s better than Stack Exchange? Stack Exchange in real life.</p>
<p>Rewind a year, and I worked for a company on the outskirts of Leeds.  Leeds is a big digital hub, and there were a large number of events to pick and choose from.  <a href="https://twitter.com/leedsphp">LeedsPHP</a>, <a href="https://twitter.com/leedsjs">https://twitter.com/leedsjs</a> and <a href="https://twitter.com/wpleeds">WPLeeds</a> to name only a few.</p>
<p>Whilst I worked in Leeds, I live in York; and whilst it&#8217;s not too much of a trek to Leeds, there are very few user groups here &#8211; and I was still missing the one important thing I was looking for &#8211; a Magento User Group.</p>
<p>When I moved jobs to York, my manager and I decided that it was about time to do something. So we set up MagentoYork.  It does exactly what it says on the tin. It&#8217;s a Magento user group, for people based in / around York, and we meet in York once every two months or so.</p>
<p>Once we had a venue sorted (a pub), we needed to find people to talk with.</p>
<p>We set up a group on Meetup.com, and I fired off a few emails and submitted the odd contact form to local companies that I knew produced Magento sites.</p>
<p>Luckily, our first meeting had more attendees than just myself and my team.  Not many more mind, but, once we met and talked, we all had a better understanding of who we were, why we were there, and what we thought we could get out of the group.</p>
<p>With the meetings in Leeds, I was a nomad, only surfacing for the odd meeting, and really not getting behind them and being at one with them.  However, my attendance did make me think about what I wanted from other user groups &#8211; and ultimately what I wanted out of MagentoYork.</p>
<p>So what kind of things can you expect to get out of a user group?</p>
<ul>
<li>Talks. Usually the backbone of a &#8220;structured&#8221; usergroup.  In the case of MagentoYork, we try to keep technical and marketing angles covered to give variety for developers and merchants.  Essentially, all developers are merchants to some degree &#8211; and all merchants are developers.</li>
<li>Discussion.  Discussing approaches broadens your problem solving skills. We&#8217;re a very open group.  There&#8217;s no right and wrong a lot of the time.</li>
<li>Wider topic discussion.  Magento is a huge topic to cover, but add to that running a department, running a company, finding business, discussing your favourite editor, your preferences for deployment and development &#8211; there is a huge range of topics that can be discussed.</li>
<li>Feeling part of something bigger.  Giving back and helping the Magento community that supplies so much knowledge and power &#8211; all mostly free of charge.</li>
<li>Speaking experience for those so inclined.  Usually to a lovely bunch of folk who, over a short number of meetings, can become friends</li>
</ul>
<p>I would strongly encourage you to find a user group in your area for something you are passionate about.  Can&#8217;t find one? Create one!  Unless you&#8217;re into something very niche, there will be likeminded people out there.</p>
<p>It&#8217;s important that once you&#8217;ve found a user group, you contribute.  That might just be turning up to each meeting, or it might be speaking, organising or promoting it to others.</p>
<p>The post <a href="https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/">Setting up a Magento Users Group &#8211; Why finding like-minded people is so important</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/setting-magento-users-group-finding-like-minded-people-important/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Tips for running Docker on Windows Part I</title>
		<link>https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/</link>
					<comments>https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/#comments</comments>
		
		<dc:creator><![CDATA[Douglas Radburn]]></dc:creator>
		<pubDate>Tue, 26 Jan 2016 20:20:16 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Docker]]></category>
		<guid isPermaLink="false">http://www.douglasradburn.co.uk/?p=1701</guid>

					<description><![CDATA[<p>I&#8217;m a little late to the Docker party. However, we&#8217;re experiencing an increasing number of issues (mostly speed) with our current development environment (VMs), so I really wanted to explore <a class="read-more" href="https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/">Read More &#187;</a></p>
<p>The post <a href="https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/">Tips for running Docker on Windows Part I</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I&#8217;m a little late to the <a href="https://docs.docker.com/">Docker</a> party. However, we&#8217;re experiencing an increasing number of issues (mostly speed) with our current development environment (VMs), so I really wanted to explore an alternative.</p>
<p>The beauty of Docker, is that we have the speed of the host system for accessing our source code (no more mounted drives), but also, the flexibility of running our stack and swapping out any components we need &#8211; don&#8217;t want to use Apache anymore? Switch out the container for Nginx, back up and running in minutes.</p>
<p>So, over the Christmas period, I started exploring Docker on Mac, and then, in January, started exploring Docker on Windows.</p>
<h2>Access Windows mounted directories directly from within a Docker container</h2>
<p>You cannot mount a windows directory from directly within your container, due to the DockerMachine (running as a VM).  Whilst you can mount a volume using something like <code>docker run -it -v /docker-data:/data debian bash</code> you can only do this if the <code>/docker-data</code> is within your <code>Users</code> folder &#8211; Kitematic will tell you this if you try and add a directory .</p>
<p>So, what if you want to outside of your <code>Users</code> folder? It&#8217;s a little more complicated.</p>
<p>Firstly, you&#8217;re going to want to close down your Docker VM.  Simply open up VirtualBox, find the VM called <code>Default</code> and shut it down.</p>
<p>Navigate to your VirtualBox folder in a command prompt &#8211; <code>cd "C:\Program Files\Oracle\VirtualBox"</code></p>
<p>Next, run the VirtualBox Media Manager and add a new folder to your VM.</p>
<p><code>VBoxManage sharedfolder add default --name <dataname> --hostpath "<directory: \folder>"</directory:></dataname></code> so, something like: <code>VBoxManage sharedfolder add default --name mydata --hostpath "d:\development\"</code></p>
<p><code>mydata</code> is going to be the name of our data folder. So you&#8217;ll be able to use whatever name you want here.</p>
<p>Now, start your VM.</p>
<p>Next, you should SSH into your Docker VM.  Open up a Docker Terminalm and <code>ssh docker@192.168.99.100</code></p>
<p>The default Docker installation login details are:</p>
<p>user: docker<br />
pass: tcuser</p>
<p>Now that you&#8217;re on the Docker machine, you need to run the following:</p>
<p><code>sudo mkdir -p /data</code><br />
<code>sudo mount -t vboxsf -o "defaults,uid=33,gid=33,rw" mydata /data</code></p>
<p>Next, we need to tell Docker to mount this every time we start the VM.</p>
<p><code>sudo vi /mnt/sda1/var/lib/boot2docker/profile</code></p>
<p>Once you&#8217;re editing the file, add the following to the end.</p>
<p><code>mount -t vboxsf -o "defaults,uid=33,gid=33,rw" mydata /data</code></p>
<p>Once added, you can then add the volume to any container like so:</p>
<p><code>docker run --name php56 -p 80:80 -v /data/www/:/var/www/html -d localphp:5.6</code></p>
<p>Where <code>/data/www/</code> is actually <code>d:\development\data\</code></p>
<p>The post <a href="https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/">Tips for running Docker on Windows Part I</a> appeared first on <a href="https://www.douglasradburn.co.uk">Independent technical advice for ecommerce projects based in Yorkshire</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.douglasradburn.co.uk/tips-for-running-docker-on-windows-part-i/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Object Caching 14/154 objects using Disk
Page Caching using Disk: Enhanced (SSL caching disabled) 
Minified using Disk

Served from: douglasradburn.co.uk @ 2026-09-29 16:15:42 by W3 Total Cache
-->