<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Subhashree Ayyappan</title>
    <description>The latest articles on DEV Community by Subhashree Ayyappan (@subhashreeayyappan).</description>
    <link>https://dev.to/subhashreeayyappan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3965431%2Fe091d47c-34b3-4e3e-abb2-e16bffe053f4.png</url>
      <title>DEV Community: Subhashree Ayyappan</title>
      <link>https://dev.to/subhashreeayyappan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/subhashreeayyappan"/>
    <language>en</language>
    <item>
      <title>How Agile Octopus Pricing Actually Works (And Is It Worth the Hassle?)</title>
      <dc:creator>Subhashree Ayyappan</dc:creator>
      <pubDate>Mon, 08 Jun 2026 23:53:38 +0000</pubDate>
      <link>https://dev.to/subhashreeayyappan/how-agile-octopus-pricing-actually-works-and-is-it-worth-the-hassle-4bh6</link>
      <guid>https://dev.to/subhashreeayyappan/how-agile-octopus-pricing-actually-works-and-is-it-worth-the-hassle-4bh6</guid>
      <description>&lt;p&gt;I just switched to Octopus Energy about a month ago. Haven't moved to Agile yet because I'm still waiting for my smart meter installation. But I've spent way too much time researching how the pricing works, so figured I'd write up what I've learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;Most electricity tariffs charge you the same rate no matter what time of day it is. Boil the kettle at 3am or 6pm, same price.&lt;/p&gt;

&lt;p&gt;Agile is different. The price changes every 30 minutes. And you know what tomorrow's prices will be in advance, because Octopus publishes them around 4pm each day.&lt;/p&gt;

&lt;p&gt;That's the bit that surprised me. I assumed it was some kind of real-time spot pricing where you're gambling on costs. It's not. You can literally look at tomorrow's prices tonight and plan around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the prices come from
&lt;/h2&gt;

&lt;p&gt;So how does Octopus decide what to charge every half hour?&lt;/p&gt;

&lt;p&gt;They follow the wholesale electricity market. Here's what happens:&lt;/p&gt;

&lt;p&gt;Power generators (wind farms, gas plants, nuclear, solar) sell electricity on the day-ahead market through auctions. The price for each half-hour slot gets set the day before. Octopus takes that wholesale price, adds their margin plus network charges and policy costs, and that becomes your Agile rate.&lt;/p&gt;

&lt;p&gt;The wholesale price is basically driven by supply and demand. Loads of wind and nobody using electricity at 3am? Cheap. Everyone gets home at 5:30pm and turns on the oven, heating, and TV? Expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Negative prices are real
&lt;/h2&gt;

&lt;p&gt;This is the part that got me interested in Agile in the first place.&lt;/p&gt;

&lt;p&gt;Sometimes the price goes below zero. As in, you get paid to use electricity. Sounds too good to be true but there's a boring practical reason for it.&lt;/p&gt;

&lt;p&gt;Nuclear plants can't easily switch off. Wind farms get paid whether the grid needs their output or not. So when it's very windy overnight and demand is low, there's more electricity being generated than anyone wants. Generators end up paying to get rid of it, and that negative price passes through to Agile customers.&lt;/p&gt;

&lt;p&gt;It doesn't happen every day. But it's not rare either. Windy weekends and sunny spring afternoons are the common ones. When it happens, that's the time to stick the washing machine on or charge the car.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily pattern
&lt;/h2&gt;

&lt;p&gt;Even though the actual prices change daily, the shape of the day is fairly predictable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Midnight to 6am&lt;/strong&gt; is almost always the cheapest. Demand is rock bottom. Prices regularly drop below 10p/kWh. This is when you want to run anything you can schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10am to 3pm&lt;/strong&gt; is mid-range. Solar generation helps keep things reasonable, especially in summer months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4pm to 7pm&lt;/strong&gt; is where it gets painful. Everyone's home, cooking, heating. This is the peak. Prices can hit 30-40p/kWh, sometimes higher in winter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After 8pm&lt;/strong&gt; things start calming down again.&lt;/p&gt;

&lt;p&gt;The takeaway is pretty simple. You don't need to rewire your life around this. Just avoid running heavy stuff during 4-7pm and you'll save money. Stick the dishwasher on before bed instead of after dinner. That's basically it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before switching
&lt;/h2&gt;

&lt;p&gt;A smart meter. Specifically SMETS2, which is the current standard. Without one, Octopus can't measure your usage in half-hourly chunks and can't bill you on Agile.&lt;/p&gt;

&lt;p&gt;If you've just joined Octopus, you can book a smart meter installation through the app. It's free but expect a few weeks wait. That's where I am right now.&lt;/p&gt;

&lt;p&gt;While waiting, you can still check what prices look like in your area. The Octopus API is publicly available, and there are dashboards that show live and historical Agile rates across all 14 UK regions. I built &lt;a href="https://ukelectricityprices.co.uk" rel="noopener noreferrer"&gt;one&lt;/a&gt; while I was researching all this, partly to understand the data and partly because the existing tools didn't show me what I wanted to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it actually cheaper though?
&lt;/h2&gt;

&lt;p&gt;This is the question. And honestly, it depends on one thing: can you shift any of your usage away from the evening peak?&lt;/p&gt;

&lt;p&gt;The Ofgem price cap right now is 24.5p/kWh. On most days, the Agile daily average comes in under that. But not always. And the average doesn't tell the whole story because nobody uses electricity evenly across the day.&lt;/p&gt;

&lt;p&gt;Here's how I think about it:&lt;/p&gt;

&lt;p&gt;If you run appliances overnight or during cheap midday slots, you'll save. Probably noticeably. People who do even basic stuff like running the washing machine at 10pm instead of 6pm seem to come out ahead over a year.&lt;/p&gt;

&lt;p&gt;If literally all your usage happens between 4-7pm and you can't change that, Agile might cost you more on some days. Though even then, the cheap overnight and summer prices can offset it.&lt;/p&gt;

&lt;p&gt;Seasonally, summer is great on Agile. Prices are low across the board. Winter evenings are the expensive bit. But even in January, overnight rates tend to be well below the price cap.&lt;/p&gt;

&lt;h2&gt;
  
  
  My plan
&lt;/h2&gt;

&lt;p&gt;Once the smart meter goes in, I'm going to keep it simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Washing machine and dishwasher on delay start, set to run overnight&lt;/li&gt;
&lt;li&gt;Charge phone and laptop overnight&lt;/li&gt;
&lt;li&gt;Try to avoid the 4-7pm peak for anything heavy&lt;/li&gt;
&lt;li&gt;Check next-day prices each afternoon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing dramatic. Not setting alarms for 3am to do laundry. Just small adjustments.&lt;/p&gt;

&lt;p&gt;I'll probably write a follow-up after a few months with actual numbers on whether it saved anything meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://octopus.energy/smart/agile/" rel="noopener noreferrer"&gt;Octopus Energy Agile tariff details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ukelectricityprices.co.uk" rel="noopener noreferrer"&gt;Live Agile rates and savings calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.octopus.energy" rel="noopener noreferrer"&gt;Octopus public API&lt;/a&gt; if you want to pull the data yourself&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>energy</category>
      <category>uk</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Live UK Electricity Price Dashboard. Here's What Went Wrong (and Right)</title>
      <dc:creator>Subhashree Ayyappan</dc:creator>
      <pubDate>Tue, 02 Jun 2026 23:16:15 +0000</pubDate>
      <link>https://dev.to/subhashreeayyappan/i-built-a-live-uk-electricity-price-dashboard-heres-what-went-wrong-and-right-19l2</link>
      <guid>https://dev.to/subhashreeayyappan/i-built-a-live-uk-electricity-price-dashboard-heres-what-went-wrong-and-right-19l2</guid>
      <description>

&lt;p&gt;I'm on Agile Octopus, where electricity prices change every 30 minutes. Every evening I'd open the Octopus app trying to figure out when to run the dishwasher. The app shows you the rates, but not "the cheapest 2-hour window tonight", which is the thing you actually need to know when you're standing in the kitchen at 8pm.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://ukelectricityprices.co.uk" rel="noopener noreferrer"&gt;ukelectricityprices.co.uk&lt;/a&gt;. A dashboard that shows live half-hourly prices, finds the cheapest slots for appliances, and compares tariffs across all 14 UK regions. Next.js 16, TypeScript, Tailwind, deployed on Vercel. No database. It pulls live from the Octopus API on each request.&lt;/p&gt;

&lt;p&gt;This is the story of building it, including the bugs that cost me the most time.&lt;/p&gt;

&lt;h2&gt;
  
  
  "The API is free? Seriously?"
&lt;/h2&gt;

&lt;p&gt;The Octopus Energy API is public, well-documented, and doesn't need an API key for tariff data. You just hit &lt;code&gt;https://api.octopus.energy/v1/products/&lt;/code&gt; and you're off. I had half-hourly Agile rates rendering in a table within an hour of starting.&lt;/p&gt;

&lt;p&gt;Then two weeks later, the dashboard silently broke. No errors. Just empty data.&lt;/p&gt;

&lt;p&gt;Turns out Octopus periodically releases new Agile product versions. The product code I'd hardcoded (&lt;code&gt;AGILE-FLEX-22-11-25&lt;/code&gt;) was no longer active. The API didn't return an error. It just returned nothing.&lt;/p&gt;

&lt;p&gt;The fix: query the products endpoint first, find the currently active Agile product, then use that code for the rates call. One extra API call, but I'll never have to manually update a config again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson: if an API has versioned product codes, don't hardcode them.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug That Only Appeared at 12:40am
&lt;/h2&gt;

&lt;p&gt;Everything worked perfectly for weeks. Then one night I noticed the dashboard was showing yesterday's prices at 00:40.&lt;/p&gt;

&lt;p&gt;The Octopus API returns timestamps in UTC. "Today" on the dashboard means today in London time. Most of the year that's fine. But during British Summer Time, 00:40 BST is actually 23:40 UTC the previous day. My code was asking the API for "today in UTC" and getting yesterday's London prices.&lt;/p&gt;

&lt;p&gt;I burned a full evening on this. The fix uses &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; to figure out London's UTC offset dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sv-SE locale reliably gives YYYY-MM-DD format&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;londonToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sv-SE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Europe/London&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Check what hour London shows at noon UTC&lt;/span&gt;
&lt;span class="c1"&gt;// (noon is safe, never hits a DST boundary)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noonUTC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;T12:00:00Z`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;londonHour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-GB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Europe/London&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noonUTC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsetHours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;londonHour&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 in BST, 0 in GMT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sv-SE&lt;/code&gt; locale trick for getting ISO date strings looks weird but works everywhere. Found it on Stack Overflow after three other approaches all had edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Cheapest Window
&lt;/h2&gt;

&lt;p&gt;The feature people actually use most is "when's the cheapest time to run my washing machine?" A washing machine cycle is about 2 hours, so I need the cheapest 4 consecutive half-hour slots.&lt;/p&gt;

&lt;p&gt;It's a sliding window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findBestWindowN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgileSlot&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestAvg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;bestAvg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;bestAvg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;bestStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;bestStart&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;validFrom&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;endTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;bestStart&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;validTo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;avgPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bestAvg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also shows the most expensive window so you can see what you'd pay if you ran the dishwasher at 5pm like a normal person. The difference is usually 3-5x.&lt;/p&gt;

&lt;h2&gt;
  
  
  14 Regions, Wildly Different Prices
&lt;/h2&gt;

&lt;p&gt;I didn't realise UK electricity regions varied so much until I built the comparison page. Sometimes the cheapest region is half the price of the most expensive at the same time of day. The region code is just a letter (A through P, skipping I and O). I handle it through URL search params rather than cookies, so when someone shares a link the region comes with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEO Mistake That Wasted Weeks
&lt;/h2&gt;

&lt;p&gt;This one still annoys me. I set a canonical URL in the root layout pointing to the homepage. Every page on the site (&lt;code&gt;/deals&lt;/code&gt;, &lt;code&gt;/compare&lt;/code&gt;, &lt;code&gt;/tips/...&lt;/code&gt;) was telling Google "the real version of this page is the homepage." Google treated all 28 pages as duplicates and only indexed one.&lt;/p&gt;

&lt;p&gt;I spent weeks wondering why Search Console showed "Alternate page with proper canonical tag" on everything. The fix was two lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;metadataBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ukelectricityprices.co.uk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nx"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each page gets its own canonical. Indexing started improving within days. I could have saved a month of SEO progress if I'd gotten this right from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs to Run
&lt;/h2&gt;

&lt;p&gt;Nothing. Vercel free tier. Octopus API is free. Carbon Intensity API is free. The domain is about ten quid a year. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache API responses from day one.&lt;/strong&gt; I added Redis caching late. A 5-minute cache cuts response times and is polite to their servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never hardcode anything a provider might change.&lt;/strong&gt; Product codes, tariff names, rate structures. Fetch them dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test during BST.&lt;/strong&gt; Timezone bugs only show up when you're not looking for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get canonical URLs right immediately.&lt;/strong&gt; Self-inflicted SEO wound that took weeks to notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ukelectricityprices.co.uk" rel="noopener noreferrer"&gt;ukelectricityprices.co.uk&lt;/a&gt;. Free, no sign-up. If you're on Agile Octopus, the appliance timer and EV charging optimiser are worth a look.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>typescript</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
