<?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: 조세윤</title>
    <description>The latest articles on DEV Community by 조세윤 (@_20339d63fb9c39d841351).</description>
    <link>https://dev.to/_20339d63fb9c39d841351</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%2F4232330%2Fa111f687-c58b-4f02-957b-f1465b13bae0.png</url>
      <title>DEV Community: 조세윤</title>
      <link>https://dev.to/_20339d63fb9c39d841351</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_20339d63fb9c39d841351"/>
    <language>en</language>
    <item>
      <title>My Gemini calls failed from Korea with "User location is not supported". A Durable Object fixed it.</title>
      <dc:creator>조세윤</dc:creator>
      <pubDate>Sat, 07 Nov 2026 15:12:08 +0000</pubDate>
      <link>https://dev.to/_20339d63fb9c39d841351/my-gemini-calls-failed-from-korea-with-user-location-is-not-supported-a-durable-object-fixed-it-27f3</link>
      <guid>https://dev.to/_20339d63fb9c39d841351/my-gemini-calls-failed-from-korea-with-user-location-is-not-supported-a-durable-object-fixed-it-27f3</guid>
      <description>&lt;p&gt;I’m building hihu, an app for learning how to prompt AI. It’s just me working on it, from Korea. Each lesson includes a chat with Gemini, with the requests going through a Cloudflare Worker so the API key stays out of the app.&lt;/p&gt;

&lt;p&gt;While testing on my phone, I kept getting this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;400 User location is not supported
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About half my messages failed. I could send a sentence, get the error, then send the exact same sentence again and get a normal reply.&lt;/p&gt;

&lt;p&gt;It turned out to depend on where the request went through Cloudflare. My requests from Korea were going through either Hong Kong or Tokyo. The Tokyo requests worked. The Hong Kong ones failed because the Gemini API isn’t available there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Placement didn’t fix it
&lt;/h3&gt;

&lt;p&gt;I first tried the &lt;code&gt;[placement]&lt;/code&gt; setting in &lt;code&gt;wrangler.toml&lt;/code&gt;, hoping it would move the Worker somewhere Gemini would accept requests from.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;cf-placement&lt;/code&gt; response header showed &lt;code&gt;remote-OMA&lt;/code&gt;, which suggested the Worker was running in Omaha. That looked promising, but the Gemini calls were still leaving from Hong Kong and failing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What finally worked
&lt;/h3&gt;

&lt;p&gt;I moved the Gemini call into a Durable Object and gave it a &lt;code&gt;locationHint&lt;/code&gt; of &lt;code&gt;wnam&lt;/code&gt;, for western North America. The Worker now passes requests to that object instead of calling Gemini itself.&lt;/p&gt;

&lt;p&gt;Here’s the relevant config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;durable_objects&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bindings&lt;/span&gt;&lt;span class="k"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"US_EXIT"&lt;/span&gt;
&lt;span class="n"&gt;class_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"UsExit"&lt;/span&gt;

&lt;span class="k"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="k"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;
&lt;span class="n"&gt;new_sqlite_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"UsExit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object doesn’t do much. It takes the request body and forwards it to Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsExit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://generativelanguage.googleapis.com/v1beta/interactions&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-goog-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;US_EXIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;US_EXIT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;idFromName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&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;locationHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wnam&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://exit/&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;Before this change, all 12 requests I tested through Hong Kong failed. After the change, all 12 succeeded. Replies took about four seconds.&lt;/p&gt;

&lt;p&gt;That’s a small test, but it fixed the issue I was seeing on my phone.&lt;/p&gt;

&lt;p&gt;A few details if you’re trying this yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This works on the free plan with SQLite-backed Durable Objects (&lt;code&gt;new_sqlite_classes&lt;/code&gt; in the config).&lt;/li&gt;
&lt;li&gt;The location hint is a preference, not a guarantee, and it only applies when the object is first created. Adding it to an existing object won’t move that object.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;idFromName('us')&lt;/code&gt; sends everything through the same object. That’s enough for my app right now. I haven’t tested it under heavy traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is &lt;a href="https://hihu.hihu-app.workers.dev" rel="noopener noreferrer"&gt;hihu&lt;/a&gt;. There are only two lessons so far, both in English, and each takes a few minutes. This was the bug I had to sort out to get the chat working reliably.&lt;br&gt;
!I built hihu with a lot of help from AI coding tools, and AI helped me write this post in English.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>gemini</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
