CINXE.COM
エキサイト TechBlog.
<!DOCTYPE html> <html lang="ja" data-admin-domain="//blog.hatena.ne.jp" data-admin-origin="https://blog.hatena.ne.jp" data-author="excitech" data-avail-langs="ja en" data-blog="excitech.hatenablog.com" data-blog-host="excitech.hatenablog.com" data-blog-is-public="1" data-blog-name="エキサイト TechBlog." data-blog-owner="excitech" data-blog-show-ads="" data-blog-show-sleeping-ads="" data-blog-uri="https://tech.excite.co.jp/" data-blog-uuid="26006613704646990" data-blogs-uri-base="https://tech.excite.co.jp" data-brand="devblog" data-data-layer="{"hatenablog":{"admin":{},"analytics":{"brand_property_id":"","measurement_id":"","non_sampling_property_id":"","property_id":"","separated_property_id":"UA-29716941-19"},"blog":{"blog_id":"26006613704646990","content_seems_japanese":"true","disable_ads":"custom_domain","enable_ads":"false","enable_keyword_link":"true","entry_show_footer_related_entries":"true","force_pc_view":"true","is_public":"true","is_responsive_view":"true","is_sleeping":"false","lang":"ja","name":"\u30a8\u30ad\u30b5\u30a4\u30c8 TechBlog.","owner_name":"excitech","uri":"https://tech.excite.co.jp/"},"brand":"devblog","page_id":"index","permalink_entry":null,"pro":"pro","router_type":"blogs"}}" data-device="pc" data-dont-recommend-pro="false" data-global-domain="https://hatena.blog" data-globalheader-color="b" data-globalheader-type="pc" data-has-touch-view="1" data-help-url="https://help.hatenablog.com" data-no-suggest-touch-view="1" data-page="index" data-parts-domain="https://hatenablog-parts.com" data-plus-available="1" data-pro="true" data-router-type="blogs" data-sentry-dsn="https://03a33e4781a24cf2885099fed222b56d@sentry.io/1195218" data-sentry-environment="production" data-sentry-sample-rate="0.1" data-static-domain="https://cdn.blog.st-hatena.com" data-version="15b44d6f3386d0946efd031c34207d" data-initial-state="{}" > <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="Hatena::Bookmark" content="nocomment"/> <meta name="robots" content="max-image-preview:large" /> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=7; IE=9; IE=10; IE=11" /> <title>エキサイト TechBlog.</title> <link rel="canonical" href="https://tech.excite.co.jp/"/> <meta itemprop="name" content="エキサイト TechBlog."/> <meta itemprop="image" content="https://cdn.user.blog.st-hatena.com/default_entry_og_image/158188417/1617181591812759"/> <meta property="og:title" content="エキサイト TechBlog."/> <meta property="og:type" content="blog"/> <meta property="og:url" content="https://tech.excite.co.jp/"/> <meta property="og:image" content="https://cdn.image.st-hatena.com/image/scale/d66402fd80caf690f233820119a19fec37ca3eca/backend=imagemagick;enlarge=0;height=1000;version=1;width=1200/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fdefault_entry_og_image%2F158188417%2F1617181591812759"/> <meta property="og:image:alt" content="エキサイト TechBlog."/> <meta property="og:description" content="エキサイト TechBlog." /> <meta property="og:site_name" content="エキサイト TechBlog."/> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:image" content="https://cdn.user.blog.st-hatena.com/default_entry_og_image/158188417/1617181591812759" /> <meta name="twitter:title" content="エキサイト TechBlog." /> <meta name="twitter:description" content="エキサイト TechBlog." /> <meta name="twitter:app:name:iphone" content="はてなブログアプリ" /> <meta name="twitter:app:id:iphone" content="583299321" /> <meta name="twitter:app:url:iphone" content="hatenablog:///open?uri=https%3A%2F%2Ftech.excite.co.jp%2F" /> <meta name="twitter:site" content="@excite_tech" /> <meta name="google-site-verification" content="google-site-verification: googled37839eb2a4ffd65.html" /> <script id="embed-gtm-data-layer-loader" data-data-layer-page-specific="" > (function() { function loadDataLayer(elem, attrName) { if (!elem) { return {}; } var json = elem.getAttribute(attrName); if (!json) { return {}; } return JSON.parse(json); } var globalVariables = loadDataLayer( document.documentElement, 'data-data-layer' ); var pageSpecificVariables = loadDataLayer( document.getElementById('embed-gtm-data-layer-loader'), 'data-data-layer-page-specific' ); var variables = [globalVariables, pageSpecificVariables]; if (!window.dataLayer) { window.dataLayer = []; } for (var i = 0; i < variables.length; i++) { window.dataLayer.push(variables[i]); } })(); </script> <!-- Google Tag Manager --> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-P4CXTW');</script> <!-- End Google Tag Manager --> <link rel="shortcut icon" href="https://tech.excite.co.jp/icon/favicon"> <link rel="apple-touch-icon" href="https://tech.excite.co.jp/icon/touch"> <link rel="icon" sizes="192x192" href="https://tech.excite.co.jp/icon/link"> <link rel="alternate" type="application/atom+xml" title="Atom" href="https://tech.excite.co.jp/feed"/> <link rel="alternate" type="application/rss+xml" title="RSS2.0" href="https://tech.excite.co.jp/rss"/> <link rel="author" href="http://www.hatena.ne.jp/excitech/"> <link rel="stylesheet" type="text/css" href="https://cdn.blog.st-hatena.com/css/blog.css?version=15b44d6f3386d0946efd031c34207d"/> <link rel="stylesheet" type="text/css" href="https://usercss.blog.st-hatena.com/blog_style/26006613704646990/c51fe6472c21d8fbd8ccb8a75ab0dd4f6c68a5c5"/> <script> </script> <style> div#google_afc_user, div.google-afc-user-container, div.google_afc_image, div.google_afc_blocklink { display: block !important; } </style> <script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"エキサイト TechBlog.","url":"https://tech.excite.co.jp/"}</script> <meta name="google-site-verification" content="Mz1mDPIgY5q9hWYDEU3kng4f3C76I7lTuinylzyC9B8" /> </head> <body class="page-index enable-top-editarea globalheader-ng-enabled"> <div id="globalheader-container" data-brand="hatenablog" > <iframe id="globalheader" height="37" frameborder="0" allowTransparency="true"></iframe> </div> <nav class=" blog-controlls "> <div class="blog-controlls-blog-icon"> <a href="https://tech.excite.co.jp/"> <img src="https://cdn.image.st-hatena.com/image/square/26aa82bccf5635c9e24faceea11d379873531b75/backend=imagemagick;height=128;version=1;width=128/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fblog_custom_icon%2F158188417%2F1617181332732457" alt="エキサイト TechBlog."/> </a> </div> <div class="blog-controlls-title"> <a href="https://tech.excite.co.jp/">エキサイト TechBlog.</a> </div> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_topright_button&utm_medium=button" class="blog-controlls-subscribe-btn test-blog-header-controlls-subscribe"> 読者になる </a> </nav> <div id="container"> <div id="container-inner"> <header id="blog-title" data-brand="hatenablog"> <div id="blog-title-inner" > <div id="blog-title-content"> <h1 id="title"><a href="https://tech.excite.co.jp/">エキサイト TechBlog.</a></h1> </div> </div> </header> <div id="top-editarea"> <style type="text/css"> .header-image-wrapper {display:none;} #blog-title{display:none;} .headernew img{ max-width:100%; margin: auto; display: block; } .headernew a{ display:block; background-color:#ffff; } .headernew{ margin:0px!important; } </style> <h1 class="headernew"> <a href="https://tech.excite.co.jp/"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite_ny/20211006/20211006141648_original.png" title="" class="hatena-fotolife" itemprop="image" width="100%" /> </a> </h1> </div> <div id="content" class="hfeed" > <div id="content-inner"> <div id="wrapper"> <div id="main"> <div id="main-inner"> <!-- google_ad_section_start --> <!-- rakuten_ad_target_begin --> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-4000 words-400 mode-markdown entry-odd" id="entry-6802418398340282886" data-keyword-campaign="" data-uuid="6802418398340282886" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/04/01" rel="nofollow"> <time datetime="2025-04-01T03:30:03Z" title="2025-04-01T03:30:03Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">04</span><span class="hyphen">-</span><span class="date-day">01</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/04/01/123003" class="entry-title-link bookmark">高速なテンプレートエンジンJTEをSpringBootで試す</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p>エキサイト株式会社メディアプラットフォーム事業部エンジニア佐々木です。SpringBootの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D5%A5%A1%A5%AF%A5%C8">デファクト</a>のテンプレートエンジンはThymeleafだと思いますが、<a href="https://jte.gg/:title">JTE</a>というテンプレートエンジンもここ最近Spring Initilizrで選べるようになりました。 プリ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>できることなどで、Thymeleafより10倍以上パフォーマンスがでるようです。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%D5%A5%A3%A5%C3%A5%AF">トラフィック</a>が多いサービスを多く抱えているので、試してみます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjte.gg%2F" title="jte: Java Template Engine - jte" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://jte.gg/">jte.gg</a></cite></p> <ul class="table-of-contents"> <li><a href="#ディレクトリ構成">ディレクトリ構成</a></li> <li><a href="#buildgradleの設定">build.gradleの設定</a></li> <li><a href="#applicationproperties">application.properties</a></li> <li><a href="#Javaコード部分">Javaコード部分</a></li> <li><a href="#テンプレート">テンプレート</a></li> <li><a href="#レイアウトファイル">レイアウトファイル</a><ul> <li><a href="#レイアウトファイルの作成">レイアウトファイルの作成</a></li> <li><a href="#レイアウトファイルを使用する">レイアウトファイルを使用する</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="ディレクトリ構成"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ構成</h2> <pre class="code" data-lang="" data-unlink>├── build.gradle.kts ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── net ├── jte │ ├── .jteroot │ ├── index.jte │ └── layout.jte └── resources ├── application.properties</pre> <h2 id="buildgradleの設定">build.gradleの設定</h2> <p>依存関係は、<a href="https://start.spring.io/#!type=gradle-project-kotlin&language=java&platformVersion=3.4.4&packaging=jar&jvmVersion=21&groupId=jp.co.excite.sample&artifactId=jte&name=jte_sample_application&description=jte%20sample%20application&packageName=jp.co.excite.sample.jte&dependencies=devtools,configuration-processor,web,jte">Spring Initirizrで選択したもの</a>をそのまま使用しています。</p> <pre class="code lang-kotlin" data-lang="kotlin" data-unlink>plugins { id(<span class="synConstant">"gg.jte.gradle"</span>) version <span class="synConstant">"3.1.16"</span> } dependencies { <span class="synComment">// SpringBootの開発用の標準的な設定</span> implementation(<span class="synConstant">"org.springframework.boot:spring-boot-starter-web"</span>) developmentOnly(<span class="synConstant">"org.springframework.boot:spring-boot-devtools"</span>) annotationProcessor(<span class="synConstant">"org.springframework.boot:spring-boot-configuration-processor"</span>) testImplementation(<span class="synConstant">"org.springframework.boot:spring-boot-starter-test"</span>) testRuntimeOnly(<span class="synConstant">"org.junit.platform:junit-platform-launcher"</span>)} <span class="synComment">// JTEの依存関係</span> implementation(<span class="synConstant">"gg.jte:jte:3.1.16"</span>) implementation(<span class="synConstant">"gg.jte:jte-spring-boot-starter-3:3.1.16"</span>) </pre> <h2 id="applicationproperties">application.properties</h2> <p>application.propertiesは下記のような設定をします。</p> <pre class="code lang-jproperties" data-lang="jproperties" data-unlink> <span class="synIdentifier">spring.application.name</span>=<span class="synConstant">jte</span> <span class="synComment"># 開発モードで、ホットリロードを行います</span> <span class="synIdentifier">gg.jte.development-mode</span>=<span class="synConstant">true</span> <span class="synComment"># テンプレートの拡張子を決めます</span> <span class="synIdentifier">gg.jte.template-suffix</span>=<span class="synConstant">.jte</span> <span class="synComment"># コンパイルして高速するためのオプション(開発時はfalseでもいい)</span> <span class="synIdentifier">gg.jte.precompile</span>=<span class="synConstant">false</span> </pre> <h2 id="Javaコード部分"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a>コード部分</h2> <p>説明のために1ファイルに押し込んでいます。<a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a>コードの部分はThymeleafのときと同じです。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@SpringBootApplication</span> <span class="synType">public</span> <span class="synType">class</span> JteApplication { <span class="synType">public</span> <span class="synType">static</span> <span class="synType">void</span> main(String[] args) { SpringApplication.run(JteApplication.<span class="synType">class</span>, args); } <span class="synPreProc">@Controller</span> <span class="synPreProc">@RequestMapping</span>(<span class="synConstant">""</span>) <span class="synType">public</span> <span class="synType">static</span> <span class="synType">class</span> RootController { <span class="synPreProc">@RequestMapping</span>(<span class="synConstant">""</span>) <span class="synType">public</span> String index(Model model) { Info info = <span class="synStatement">new</span> Info(<span class="synConstant">"Hello, JTE!"</span>, <span class="synConstant">1</span>, List.of(<span class="synConstant">"a"</span>, <span class="synConstant">"b"</span>, <span class="synConstant">"c"</span>)); model.addAttribute(<span class="synConstant">"info"</span>, info); <span class="synStatement">return</span> <span class="synConstant">"index"</span>; } } <span class="synType">public</span> record Info(String message, <span class="synType">int</span> id, List<String> alphabet) { } } </pre> <h2 id="テンプレート">テンプレート</h2> <p>JTEテンプレートは、HTMLの中に<a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a>コードを記載できます。</p> <pre class="code lang-html" data-lang="html" data-unlink> index.jte <span class="synIdentifier"><%-- インポートするクラスを書きます --%></span> @import jp.co.excite.sample.jte.JteApplication <span class="synIdentifier"><%-- テンプレートに渡されるクラス書きます --%></span> @param JteApplication.Info info <span class="synIdentifier"><</span><span class="synStatement">h1</span><span class="synIdentifier">></span>hello world3<span class="synIdentifier"></</span><span class="synStatement">h1</span><span class="synIdentifier">></span> info: ${info.message()} @if(info.id() <span class="synIdentifier"><</span> 2<span class="synIdentifier">)</span> <span class="synIdentifier"> </span><span class="synError"><</span><span class="synIdentifier">h2></span>id: ${info.id()}<span class="synIdentifier"></</span><span class="synStatement">h2</span><span class="synIdentifier">></span> @endif <span class="synIdentifier"><</span><span class="synStatement">ul</span><span class="synIdentifier">></span> @for(String item : info.alphabet()) <span class="synIdentifier"><</span><span class="synStatement">li</span><span class="synIdentifier">></span>have alphabet item: ${item}<span class="synIdentifier"></</span><span class="synStatement">li</span><span class="synIdentifier">></span> @endfor <span class="synIdentifier"></</span><span class="synStatement">ul</span><span class="synIdentifier">></span> </pre> <p>上記のコードで下記のような出力になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/earu/20250329/20250329164933.png" width="472" height="512" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="レイアウトファイル">レイアウトファイル</h2> <p>レイアウトファイルも備えています。</p> <h3 id="レイアウトファイルの作成">レイアウトファイルの作成</h3> <p>サンプルのレイアウトファイルは下記のようになります。</p> <pre class="code" data-lang="" data-unlink>layout.jte @import gg.jte.Content <%-- レイアウトファイルに渡される引数を列挙します --%> @param String title @param String h2 @param Content content <head> <title>${title}</title> </head> <body> <h1> ${h2} </h1> <div class="content"> ${content} </div> </body> </pre> <p><code>@param</code> のところが異なります。<code>index.jte</code>ファイルの時とは異なり、<code>@param {型} 変数名</code> という形になっています。この場合は、このファイルに渡される引数の意味になります。</p> <h3 id="レイアウトファイルを使用する">レイアウトファイルを使用する</h3> <p>先ほどのHTMLを出力したファイルにレイアウトファイルを適用しようと思います。</p> <p><code>@template.layout(...)</code> の部分がレイアウトファイルの読み込みになります。</p> <pre class="code" data-lang="" data-unlink>@import jp.co.excite.sample.jte.JteApplication @param JteApplication.Info info @template.layout(title = "aaaa" , h2 = "hello world3" , content = @` <h2>高速なパフォーマンス</h2> <p>JTEはテンプレートを事前コンパイルしてJavaコードに変換するため、実行時の解析オーバーヘッドがなく、非常に高速に動作します。ThymeleafやFreemarkerのようなランタイム解析型のエンジンと比べてレスポンス時間が短く、特に高負荷環境で優れたパフォーマンスを発揮します。</p> <h2>Javaとのシームレスな統合</h2> <p>JTEはJavaの型システムを活用し、テンプレート内で使用する変数やメソッドの型安全性が確保されます。IDEでの補完やリファクタリングもサポートされており、Javaコードとテンプレートを効率的に開発できます。これはHTML属性ベースのエンジンにはない利点です。</p> <h2>シンプルで直感的な構文</h2> <p>JTEの構文はシンプルでHTMLに近く、学習が容易です。制御構文は必要最小限に抑えられ、@templateや@Content型で柔軟に再利用可能なテンプレートを構築できます。ロジックをJava側に分離できるため、テンプレートが複雑化しにくいのも特徴です。</p> `) <h1>Hello world!</h1> info: ${info.message()} @if(info.id() < 2) <h2>id: ${info.id()}</h2> @endif <ul> @for(String item : info.alphabet()) <li>have alphabet item: ${item}</li> @endfor </ul> </pre> <p>基本的には<a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a>で使用できる型をそのまま使えますが、テキストブロックでHTMLを書きたい場合があると思いますので、<code>Content</code>というクラスが用意されいて、<code>@``</code>構文で中に記述されたHTMLが展開されるような仕組みになっています。上記の例でも使用しています。出力は下記になります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/earu/20250329/20250329170151.png" width="954" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="まとめ">まとめ</h2> <p>JTEを使用してみましたが、機能数もすくなく、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a>がそのまま使えましたので濫用は禁物ですが便利だと思います。ホットリロードが標準装備されている点は開発体験が尊重されていて、いいとおもいました。シンプルなレイアウト機能もAlpine.jsやhtmx.jsと組み合わせやすいと個人的に感じております。なによりパフォーマンスがいいので、弊社のような高<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%D5%A5%A3%A5%C3%A5%AF">トラフィック</a>なサイトと相性はよさそうです。段階的に実験していこうと思います。</p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/java" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">java</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/springboot" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">springboot</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/jte" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">jte</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="earu" >earu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/04/01/123003"><time data-relative datetime="2025-04-01T03:30:03Z" title="2025-04-01T03:30:03Z" class="updated">2025-04-01 12:30</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_source=blogs_entry_footer&utm_campaign=subscribe_blog"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/04/01/123003" data-hatena-star-title="高速なテンプレートエンジンJTEをSpringBootで試す" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/04/01/123003" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/04/01/123003" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/04/01/123003"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=java&hashtags=springboot&hashtags=jte&text=%E9%AB%98%E9%80%9F%E3%81%AA%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3JTE%E3%82%92SpringBoot%E3%81%A7%E8%A9%A6%E3%81%99+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F04%2F01%2F123003" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-3600 words-400 mode-markdown entry-even" id="entry-6802340630906002338" data-keyword-campaign="" data-uuid="6802340630906002338" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/31" rel="nofollow"> <time datetime="2025-03-31T09:35:21Z" title="2025-03-31T09:35:21Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">31</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/31/183521" class="entry-title-link bookmark"> [htmx] hx-triggerを使ってinfinitScrollを実装する方法[Java/Spring Boot]</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20250331/20250331183456.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>こんにちは、新卒2年目の岡崎です。今回は<code>hx-trigger</code>を使ってinfinitScrollを実装する方法を紹介します。</p> <h2 id="環境">環境</h2> <ul> <li>gradle</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink>------------------------------------------------------------ Gradle <span class="synConstant">8.5</span> ------------------------------------------------------------ Build time: <span class="synConstant">2023</span>-<span class="synConstant">11</span>-<span class="synConstant">29</span> <span class="synConstant">14</span>:<span class="synConstant">08</span>:<span class="synConstant">57</span> UTC <span class="synStatement">Revision</span>: 28aca86a7180baa17117e0e5ba01d8ea9feca598 <span class="synStatement">Kotlin</span>: <span class="synConstant">1.9.20</span> <span class="synStatement">Groovy</span>: <span class="synConstant">3.0.17</span> <span class="synStatement">Ant</span>: Apache Ant(TM) version <span class="synConstant">1.10.13</span> compiled on January <span class="synConstant">4</span> <span class="synConstant">2023</span> <span class="synStatement">JVM</span>: <span class="synConstant">21.0.2</span> (Amazon.com Inc. <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS) <span class="synStatement">OS</span>: Mac OS X <span class="synConstant">12.3</span> aarch64 </pre> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a></li> </ul> <pre class="code lang-java" data-lang="java" data-unlink>openjdk version <span class="synConstant">"21.0.2"</span> <span class="synConstant">2024</span>-<span class="synConstant">01</span>-<span class="synConstant">16</span> LTS OpenJDK Runtime Environment Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS) OpenJDK <span class="synConstant">64</span>-Bit Server VM Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS, mixed mode, sharing) </pre> <ul> <li>Spring Boot</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink> . ____ _ __ _ _ /<span class="synError">\\</span> / ___'_ __ _ _(_)_ __ __ _ <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> ( ( )<span class="synError">\</span>___ | <span class="synConstant">'</span><span class="synError">_ | </span><span class="synConstant">'</span>_| | '_ <span class="synError">\</span>/ _<span class="synError">`</span> | <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\\</span>/ ___)| |_<span class="synError">)</span>| | | | | || (_| | ) <span class="synError">)</span> <span class="synError">)</span> <span class="synError">)</span> ' |____| .__|_| |_|_| |_<span class="synError">\</span>__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3<span class="synConstant">.2.1</span>) </pre> <p>今回は、htmxを使っているため、以下の依存関係を<code>build.gradle</code>に追加します。</p> <pre class="code lang-java" data-lang="java" data-unlink>implementation <span class="synConstant">"org.webjars.npm:htmx.org:1.9.10"</span> </pre> <h2 id="実装">実装</h2> <p><code>hx-trigger</code>を使うことで、infinitScrollを簡単に実装できます。</p> <p><code>hx-trigger</code>の詳細は、公式ドキュメントをご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhtmx.org%2Fattributes%2Fhx-trigger%2F" title="</> htmx ~ hx-trigger Attribute" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://htmx.org/attributes/hx-trigger/">htmx.org</a></cite></p> <h3 id="Controller">Controller</h3> <p>まずは、Controllerにエンドポイントのを作成します。</p> <ul> <li><code>/scroll</code>は、最初にアクセスした際の画面表示用エンドポイントです。</li> <li><code>/scroll/new</code>は、スクロール時に呼び出されるエンドポイントです。</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Controller</span> <span class="synPreProc">@RequestMapping</span>(<span class="synConstant">"scroll"</span>) <span class="synType">public</span> <span class="synType">class</span> ScrollController { <span class="synPreProc">@GetMapping</span> <span class="synType">public</span> String get( Model model ) { <span class="synType">final</span> List<String> list = List.of( <span class="synConstant">"test1"</span>, <span class="synConstant">"test2"</span>, <span class="synConstant">"test3"</span>, <span class="synConstant">"test4"</span>, <span class="synConstant">"test5"</span>, <span class="synConstant">"test6"</span>, <span class="synConstant">"test7"</span>, <span class="synConstant">"test8"</span>, <span class="synConstant">"test9"</span>, <span class="synConstant">"test10"</span> ); model.addAttribute(<span class="synConstant">"list"</span>, list); <span class="synStatement">return</span> <span class="synConstant">"scroll/index"</span>; } <span class="synPreProc">@GetMapping</span>(<span class="synConstant">"new"</span>) <span class="synType">public</span> String getNew(Model model) { <span class="synType">final</span> List<String> list = List.of( <span class="synConstant">"newTest1"</span>, <span class="synConstant">"newTest2"</span>, <span class="synConstant">"newTest3"</span>, <span class="synConstant">"newTest4"</span>, <span class="synConstant">"newTest5"</span>, <span class="synConstant">"newTest6"</span>, <span class="synConstant">"newTest7"</span>, <span class="synConstant">"newTest8"</span>, <span class="synConstant">"newTest9"</span>, <span class="synConstant">"newTest10"</span> ); model.addAttribute(<span class="synConstant">"list"</span>, list); <span class="synStatement">try</span> { Thread.sleep(<span class="synConstant">3000</span>); } <span class="synStatement">catch</span> (InterruptedException e) { <span class="synStatement">throw</span> <span class="synStatement">new</span> RuntimeException(e); } <span class="synStatement">return</span> <span class="synConstant">"scroll/index :: scroll"</span>; } } </pre> <h3 id="html">html</h3> <ul> <li>scroll/layout.html</li> </ul> <p>レイアウトファイルを作成しました。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!DOCTYPE html></span> <span class="synIdentifier"><</span><span class="synStatement">html</span><span class="synIdentifier"> xmlns:th=</span><span class="synConstant">"http://www.thymeleaf.org"</span><span class="synIdentifier"> </span><span class="synType">lang</span><span class="synIdentifier">=</span><span class="synConstant">"ja"</span> <span class="synIdentifier"> th:fragment=</span><span class="synConstant">"layout(content)"</span> <span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">title</span><span class="synIdentifier">></span>Demo<span class="synIdentifier"></</span><span class="synStatement">title</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/htmx.org/1.9.10/dist/htmx.min.js"</span><span class="synIdentifier">></</span><span class="synStatement">script</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">header</span><span class="synIdentifier">></span> header <span class="synIdentifier"></</span><span class="synStatement">header</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> th:replace=</span><span class="synConstant">"${content}"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">p</span><span class="synIdentifier">></span>Page content goes here<span class="synIdentifier"></</span><span class="synStatement">p</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">footer</span><span class="synIdentifier">></span> footer <span class="synIdentifier"></</span><span class="synStatement">footer</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">html</span><span class="synIdentifier">></span> </pre> <ul> <li>scroll/index.html</li> </ul> <p>画面にlistの内容を表示します。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!DOCTYPE html></span> <span class="synIdentifier"><</span><span class="synStatement">html</span><span class="synIdentifier"> xmlns:th=</span><span class="synConstant">"http://www.thymeleaf.org"</span><span class="synIdentifier"> </span><span class="synType">lang</span><span class="synIdentifier">=</span><span class="synConstant">"ja"</span><span class="synIdentifier"> th:replace=</span><span class="synConstant">"~{scroll/layout::layout(~{::content})}"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">th</span><span class="synIdentifier">:block th:fragment=</span><span class="synConstant">"content"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">th</span><span class="synIdentifier">:block th:insert=</span><span class="synConstant">"~{scroll/index::scroll}"</span><span class="synIdentifier">></</span><span class="synStatement">th</span><span class="synIdentifier">:block></span> <span class="synIdentifier"></</span><span class="synStatement">th</span><span class="synIdentifier">:block></span> <span class="synIdentifier"><</span><span class="synStatement">th</span><span class="synIdentifier">:block th:fragment=</span><span class="synConstant">"scroll"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"container text-center mt-5"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">th</span><span class="synIdentifier">:block th:each=</span><span class="synConstant">"row : ${list}"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> th:</span><span class="synType">text</span><span class="synIdentifier">=</span><span class="synConstant">"${row}"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">th</span><span class="synIdentifier">:block></span> <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">span</span><span class="synIdentifier"> hx-indicator=</span><span class="synConstant">"#indicator"</span> <span class="synIdentifier"> hx-</span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"closest div"</span> <span class="synIdentifier"> hx-trigger=</span><span class="synConstant">"revealed"</span> <span class="synIdentifier"> hx-swap=</span><span class="synConstant">"outerHTML"</span> <span class="synIdentifier"> th:hx-get=</span><span class="synConstant">"@{/scroll/new}"</span><span class="synIdentifier">></span> Loading...... <span class="synIdentifier"></</span><span class="synStatement">span</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">th</span><span class="synIdentifier">:block></span> <span class="synIdentifier"></</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">html</span><span class="synIdentifier">></span> </pre> <p>下記の部分がスクロール部分の実装です。</p> <pre class="code lang-html" data-lang="html" data-unlink> <span class="synIdentifier"><</span><span class="synStatement">span</span> <span class="synIdentifier"> hx-</span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"closest div"</span> <span class="synIdentifier"> hx-trigger=</span><span class="synConstant">"revealed"</span> <span class="synIdentifier"> hx-indicator=</span><span class="synConstant">"#indicator"</span> <span class="synIdentifier"> hx-swap=</span><span class="synConstant">"outerHTML"</span> <span class="synIdentifier"> th:hx-get=</span><span class="synConstant">"@{/scroll/new}"</span><span class="synIdentifier">></span> Loading...... <span class="synIdentifier"></</span><span class="synStatement">span</span><span class="synIdentifier">></span> </pre> <p>今回は、スクロール時にリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを発火させたいので、<code>hx-trigger</code> に <code>revealed</code> を指定しました。これにより、指定された div タグがスクロールで表示されると、<code>/scroll/new</code> が呼び出されます。</p> <p>また、<code>hx-indicator="#indicator"</code> を使用してローディング処理を実装しました。</p> <p>最後に、スクロール時の挙動を確認できたら、完了です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240913/20240913123726.gif" width="1200" height="776" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="最後に">最後に</h2> <p>今回は<code>hx-trigger</code>を使ってinfinitScrollを実装する方法を紹介しました。みなさんもぜひ実装してみてください。</p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/htmx" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">htmx</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Java" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Java</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Spring%20Boot" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Spring Boot</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="ooo-ka999" >ooo-ka999</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/31/183521"><time data-relative datetime="2025-03-31T09:35:21Z" title="2025-03-31T09:35:21Z" class="updated">2025-03-31 18:35</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_campaign=subscribe_blog&utm_source=blogs_entry_footer"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/31/183521" data-hatena-star-title=" [htmx] hx-triggerを使ってinfinitScrollを実装する方法[Java/Spring Boot]" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/31/183521" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/31/183521" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/31/183521"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=htmx&hashtags=Java&hashtags=Spring+Boot&text=+%5Bhtmx%5D+hx-trigger%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6infinitScroll%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%5BJava%2FSpring+Boot%5D+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F31%2F183521" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-middle autopagerize_page_element chars-1200 words-100 mode-markdown entry-odd" id="entry-6802418398339708663" data-keyword-campaign="" data-uuid="6802418398339708663" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date middle"> <a href="https://tech.excite.co.jp/archive/2025/03/31" rel="nofollow"> <time datetime="2025-03-31T08:00:00Z" title="2025-03-31T08:00:00Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">31</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/31/170000" class="entry-title-link bookmark">第11回テクデザBeer Bashを開催しました</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/Beer%20Bash" class="entry-category-link category-Beer-Bash">Beer Bash</a> </div> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-soichiro-yoshikawa/20250327/20250327104822.png" width="1200" height="629" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、エキサイトでエンジニアをしている吉川です。 先日3/14(金)に社内イベントの「テクデザBeer <a class="keyword" href="https://d.hatena.ne.jp/keyword/Bash">Bash</a>」を開催したので、運営視点でレポートを書いていきます。</p> <h2 id="テクデザBeer-Bashとは">テクデザBeer <a class="keyword" href="https://d.hatena.ne.jp/keyword/Bash">Bash</a>とは</h2> <p>Beer <a class="keyword" href="https://d.hatena.ne.jp/keyword/Bash">Bash</a>とはbeer(ビール)+ <a class="keyword" href="https://d.hatena.ne.jp/keyword/bash">bash</a>(にぎやかなパー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>)を合わせた造語で、真面目な部分を残しつつ、カジュアルな雰囲気で交流を行うイベントです。 年に3、4回社内カフェスペースで開催しており、同じチーム内の人はもちろん、業務ではあまり関わることがない他部署の人たちとも繋がる場になっています。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-soichiro-yoshikawa/20250331/20250331121325.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-soichiro-yoshikawa/20250331/20250331121306.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="当日のコンテンツ">当日のコンテンツ</h2> <p>前半は以下2つのメインコンテンツを発表し、後半はフリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>の時間にしました。</p> <ul> <li>録音分析の技術スタック紹介</li> <li>電話占いの文字起こしと要約</li> </ul> <h3 id="録音分析の技術スタック紹介">録音分析の技術スタック紹介</h3> <p>エキサイト電話占いやお悩み相談室については、サービスの向上のため通話内容の分析を行なっています。 サービス本体とは別に分析用の<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>アカウントを発行しており、ここでは複数<a class="keyword" href="https://d.hatena.ne.jp/keyword/AWS">AWS</a>アカウントを使った分析の全体像を発表していただきました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-soichiro-yoshikawa/20250327/20250327105808.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="電話占いの文字起こしと要約">電話占いの文字起こしと要約</h3> <p>上のコンテンツのさらに細かい部分で、文字起こしと要約を具体的にどんな技術を使って実装しているのかを発表いただきました。 どちらもAIの技術を使っていますが、通話時間に比例して音声ファイルの容量も大きくなるため、分析の難易度や料金コストも高くなります。できるだけ低コスト・短時間で処理が完了するように工夫している点についてご紹介いただきました。</p> <p>また実装は<a class="keyword" href="https://d.hatena.ne.jp/keyword/Python">Python</a>で行なっていますが、uv・Ruff・Pydanticなど最近の主要パッケージを使っているので、それについてもご紹介いただきました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-soichiro-yoshikawa/20250327/20250327105839.jpg" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="運営視点の振り返り">運営視点の振り返り</h2> <p>来年度にはオフィス移転があるため、現オフィスでは最後のイベントとなりました。 今回のコンテンツは技術的に気になる点が多かったためか、質疑応答が活発でした。コンテンツを決める際にこの辺りも今後強く意識して行きたいと思います。</p> <p>また後半のフリー<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ではいくつかの机に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>テーマを設定し、最初はそれについて話してもらうようにしました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>テーマを決めるのは大変ですが、場が盛り上がりやすい仕掛けにはなるので、引き続きブラッシュアップしていこうと思いました。</p> <h2 id="まとめ">まとめ</h2> <p>最後まで読んでいただき、ありがとうございました。 テクデザBeer <a class="keyword" href="https://d.hatena.ne.jp/keyword/Bash">Bash</a>は社内イベントではありますが、少しでもイベントの雰囲気が伝わっていたら幸いです。 25年度もテクデザBeer <a class="keyword" href="https://d.hatena.ne.jp/keyword/Bash">Bash</a>は開催予定ですので、引き続きイベントを盛り上げて行きます!!</p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Beer%20Bash" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Beer Bash</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="excite-soichiro-yoshikawa" >excite-soichiro-yoshikawa</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/31/170000"><time data-relative datetime="2025-03-31T08:00:00Z" title="2025-03-31T08:00:00Z" class="updated">2025-03-31 17:00</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/31/170000" data-hatena-star-title="第11回テクデザBeer Bashを開催しました" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/31/170000" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/31/170000" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/31/170000"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=Beer+Bash&text=%E7%AC%AC11%E5%9B%9E%E3%83%86%E3%82%AF%E3%83%87%E3%82%B6Beer+Bash%E3%82%92%E9%96%8B%E5%82%AC%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F31%2F170000" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-middle autopagerize_page_element chars-1600 words-100 mode-markdown entry-even" id="entry-6802418398340726163" data-keyword-campaign="" data-uuid="6802418398340726163" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date middle"> <a href="https://tech.excite.co.jp/archive/2025/03/31" rel="nofollow"> <time datetime="2025-03-31T07:24:07Z" title="2025-03-31T07:24:07Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">31</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/31/162407" class="entry-title-link bookmark">Tailwind CSSでbackground-sizeとbackground-positionの両方を任意の値で設定する方法</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/Tailwind%20CSS" class="entry-category-link category-Tailwind-CSS">Tailwind CSS</a> <a href="https://tech.excite.co.jp/archive/category/%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0" class="entry-category-link category-スタイリング">スタイリング</a> <a href="https://tech.excite.co.jp/archive/category/CSS" class="entry-category-link category-CSS">CSS</a> <a href="https://tech.excite.co.jp/archive/category/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89" class="entry-category-link category-フロントエンド">フロントエンド</a> </div> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saitoayumu/20250331/20250331081228.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。エキサイトでデザイナーをしている齋藤です。</p> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>で<code>background-size</code>と<code>background-position</code>の両方を任意の値で設定する方法をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#実現したいこと">実現したいこと</a></li> <li><a href="#background-sizeとbackground-positionの両方を任意の値で設定する方法">background-sizeとbackground-positionの両方を任意の値で設定する方法</a></li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#参考文献">参考文献</a></li> </ul> </li> </ul> <h2 id="実現したいこと">実現したいこと</h2> <p>冒頭、実現したいことを整理します。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>では、<code>background-size</code>と<code>background-position</code>を指定する場合は、どちらも<code>bg-</code>で始まるユーティリティクラスを使用します。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!-- background-size: cover; --></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"bg-cover"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synComment"><!-- background-position: center; --></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"bg-center"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span> </pre> <p>以下のようにユーティリティクラスの用意のない任意の値(Custom <a class="keyword" href="https://d.hatena.ne.jp/keyword/value">value</a>)を指定したいとします。</p> <pre class="code lang-css" data-lang="css" data-unlink><span class="synStatement">div</span> <span class="synIdentifier">{</span> <span class="synType">background-size</span>: <span class="synConstant">50%</span> <span class="synConstant">auto</span>; <span class="synType">background-position</span>: <span class="synConstant">bottom</span> <span class="synConstant">10px</span> <span class="synPreProc">right</span> <span class="synConstant">20px</span>; <span class="synIdentifier">}</span> </pre> <p>この際、<code>bg-[<value>]</code>の構文で設定しようとするとうまくいきません。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!-- syntax error: bg-[<value>]ではいっぺんに設定できない --></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"bg-[50%_auto_bottom_10px_right_20px]"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span> </pre> <p>一方で今回実現したいのは、<code>background-size</code>と<code>background-position</code>両方を任意の値で設定することです。</p> <h2 id="background-sizeとbackground-positionの両方を任意の値で設定する方法">background-sizeとbackground-positionの両方を任意の値で設定する方法</h2> <p><code>bg-[<value>]</code>の構文ではなく、<code>background-size</code>は<code>bg-[length:<value>]</code>を、<code>background-position</code>は<code>bg-[position:<value>]</code>を使用することで区別して任意の値を設定できます。</p> <table> <thead> <tr> <th> プロパティ </th> <th> 任意の値を設定 </th> </tr> </thead> <tbody> <tr> <td> <code>background-size</code> </td> <td> <code>bg-[length:<value>]</code> </td> </tr> <tr> <td> <code>background-position</code> </td> <td> <code>bg-[position:<value>]</code> </td> </tr> </tbody> </table> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2Fdocs%2Fbackground-size%23using-a-custom-value" title="background-size - Backgrounds" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/docs/background-size#using-a-custom-value">tailwindcss.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2Fdocs%2Fbackground-position%23using-a-custom-value" title="background-position - Backgrounds" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/docs/background-position#using-a-custom-value">tailwindcss.com</a></cite></p> <p>なお、どちらか一方の場合は従来通り<code>bg-[<value>]</code>でも指定できます。</p> <p>先にお示しした実現したい<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を表現すると次のようになります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"bg-[length:50%_auto] bg-[position:bottom_10px_right_20px]"</span><span class="synIdentifier">></</span><span class="synStatement">div</span><span class="synIdentifier">></span> </pre> <p>これで、<code>background-size</code>と<code>background-position</code>両方を任意の値で設定できます。</p> <h2 id="まとめ">まとめ</h2> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>で<code>background-size</code>と<code>background-position</code>の両方を任意の値で設定する方法をご紹介しました。</p> <p>どちらも接頭辞が<code>bg-</code>のため、困惑される方も多いのではないでしょうか。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を使用される方の一助となれば幸いです。ご精読ありがとうございました。</p> <h3 id="参考文献">参考文献</h3> <ul> <li><a href="https://tailwindcss.com/docs/background-size#using-a-custom-value">background-size - Backgrounds - Tailwind CSS</a></li> <li><a href="https://tailwindcss.com/docs/background-position#using-a-custom-value">background-position - Backgrounds - Tailwind CSS</a></li> </ul> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="saitoayumu" >saitoayumu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/31/162407"><time data-relative datetime="2025-03-31T07:24:07Z" title="2025-03-31T07:24:07Z" class="updated">2025-03-31 16:24</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_source=blogs_entry_footer&utm_campaign=subscribe_blog&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/31/162407" data-hatena-star-title="Tailwind CSSでbackground-sizeとbackground-positionの両方を任意の値で設定する方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/31/162407" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/31/162407" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/31/162407"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?text=Tailwind+CSS%E3%81%A7background-size%E3%81%A8background-position%E3%81%AE%E4%B8%A1%E6%96%B9%E3%82%92%E4%BB%BB%E6%84%8F%E3%81%AE%E5%80%A4%E3%81%A7%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F31%2F162407" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-last autopagerize_page_element chars-2400 words-200 mode-markdown entry-odd" id="entry-6802418398340768216" data-keyword-campaign="" data-uuid="6802418398340768216" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date last"> <a href="https://tech.excite.co.jp/archive/2025/03/31" rel="nofollow"> <time datetime="2025-03-31T06:21:05Z" title="2025-03-31T06:21:05Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">31</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/31/152105" class="entry-title-link bookmark">【Flutter】flutter_flavorizrを用いて環境分けをする</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p>こんにちは。エキサイトでアプリエンジニアをしている岡島です。</p> <p>今回は、Flutterプロジェクトで環境ごとに設定を分けて開発・ビルドしたい場合に便利なツール、<a href="https://pub.dev/packages/flutter_flavorizr"><code>flutter_flavorizr</code></a>を用いた環境構築方法を紹介したいと思います。</p> <p>本記事では、開発(dev)、ステージング(<a class="keyword" href="https://d.hatena.ne.jp/keyword/stg">stg</a>)、本番(prod)の3環境を想定し、<code>flutter_flavorizr</code>を使って<a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>と<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>両方の設定を自動で生成する方法を解説していきます。</p> <p>使ってみた感想としては、<a class="keyword" href="https://d.hatena.ne.jp/keyword/yaml">yaml</a>ファイルを書くだけで環境分けができ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/IDE">IDE</a>の環境設定も自動で行ってくれるのでとても便利だと感じました。</p> <ul class="table-of-contents"> <li><a href="#flutter_flavorizrとは">flutter_flavorizrとは?</a></li> <li><a href="#注意点">注意点</a></li> <li><a href="#実装手順">実装手順</a><ul> <li><a href="#flutter_flavorizr-を追加">flutter_flavorizr を追加</a></li> <li><a href="#flavorizryamlに環境を設定する">flavorizr.yamlに環境を設定する</a><ul> <li><a href="#各項目の説明">各項目の説明</a><ul> <li><a href="#ide-idea">ide: "idea"</a></li> <li><a href="#flavors各フレーバー定義">flavors:(各フレーバー定義)</a></li> <li><a href="#appname">app.name</a></li> <li><a href="#applicationId--bundleId">applicationId , bundleId</a></li> </ul> </li> </ul> </li> <li><a href="#自動生成コマンドの実行">自動生成コマンドの実行</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> <li><a href="#参考リンク">参考リンク</a></li> </ul> <h1 id="flutter_flavorizrとは">flutter_flavorizrとは?</h1> <p><a href="https://pub.dev/packages/flutter_flavorizr"><code>flutter_flavorizr</code></a> は、Flutterプロジェクトにおける マルチフレーバー対応を簡単に行うためのコード生成ツールです。</p> <p>以下のようなタスクを自動で生成・設定してくれます。</p> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>と<a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>両方のビルド設定(flavor)の自動生成</li> <li>環境ごとのアプリアイコン設定</li> <li>Firebaseの設定ファイル(<code>GoogleService-Info.plist</code> / <code>google-services.json</code>)の環境別配置</li> </ul> <p>これにより、開発者は<a class="keyword" href="https://d.hatena.ne.jp/keyword/yaml">yaml</a>ファイルに定義を書くだけで、煩雑な手作業なしに複数環境の構築が可能になります</p> <p>今回は簡単に環境分けの部分を紹介します。</p> <h1 id="注意点">注意点</h1> <p>flutter_flavorizrの自動生成ファイルによって、既存ファイルが上書きされるケースがあるため、既存のプロジェクトの導入時には注意が必要かもしれません。</p> <h1 id="実装手順">実装手順</h1> <h2 id="flutter_flavorizr-を追加">flutter_flavorizr を追加</h2> <p><code>pubspec.yaml</code>に flutter_flavorizr を追加し、 pub getします。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">dev_dependencies</span><span class="synSpecial">:</span> <span class="synIdentifier">flutter_flavorizr</span><span class="synSpecial">:</span> ^2.3.0 </pre> <pre class="code bash" data-lang="bash" data-unlink>flutter pub get</pre> <h2 id="flavorizryamlに環境を設定する">flavorizr.<a class="keyword" href="https://d.hatena.ne.jp/keyword/yaml">yaml</a>に環境を設定する</h2> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">ide</span><span class="synSpecial">:</span> <span class="synConstant">"idea"</span> <span class="synIdentifier">flavors</span><span class="synSpecial">:</span> <span class="synIdentifier">dev</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synConstant">"MyApp Dev"</span> <span class="synIdentifier">android</span><span class="synSpecial">:</span> <span class="synIdentifier">applicationId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp.dev"</span> <span class="synIdentifier">ios</span><span class="synSpecial">:</span> <span class="synIdentifier">bundleId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp.dev"</span> <span class="synIdentifier">stg</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synConstant">"MyApp Staging"</span> <span class="synIdentifier">android</span><span class="synSpecial">:</span> <span class="synIdentifier">applicationId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp.stg"</span> <span class="synIdentifier">ios</span><span class="synSpecial">:</span> <span class="synIdentifier">bundleId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp.stg"</span> <span class="synIdentifier">prod</span><span class="synSpecial">:</span> <span class="synIdentifier">app</span><span class="synSpecial">:</span> <span class="synIdentifier">name</span><span class="synSpecial">:</span> <span class="synConstant">"MyApp"</span> <span class="synIdentifier">android</span><span class="synSpecial">:</span> <span class="synIdentifier">applicationId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp"</span> <span class="synIdentifier">ios</span><span class="synSpecial">:</span> <span class="synIdentifier">bundleId</span><span class="synSpecial">:</span> <span class="synConstant">"com.example.myapp"</span> </pre> <h3 id="各項目の説明">各項目の説明</h3> <h4 id="ide-idea"><a class="keyword" href="https://d.hatena.ne.jp/keyword/ide">ide</a>: "idea"</h4> <p>これは使用している<a class="keyword" href="https://d.hatena.ne.jp/keyword/IDE">IDE</a>を指定する設定です。</p> <p>"idea" を指定すると、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android%20Studio">Android Studio</a> (<a class="keyword" href="https://d.hatena.ne.jp/keyword/IntelliJ">IntelliJ</a>ベースの<a class="keyword" href="https://d.hatena.ne.jp/keyword/IDE">IDE</a>) 向けに .idea <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下の設定ファイルが適切に更新されるようになります。</p> <p>flutter_flavorizr は <a class="keyword" href="https://d.hatena.ne.jp/keyword/IDE">IDE</a> に応じてフレーバーごとの「Run Configuration」を自動生成してくれます。</p> <p>他にも "<a class="keyword" href="https://d.hatena.ne.jp/keyword/vscode">vscode</a>" も指定可能です。</p> <h4 id="flavors各フレーバー定義">flavors:(各フレーバー定義)</h4> <p>この中で複数のフレーバー(環境)を定義します。上記では dev、<a class="keyword" href="https://d.hatena.ne.jp/keyword/stg">stg</a>、prod の3つを定義しています。</p> <h4 id="appname">app.name</h4> <p>各環境のアプリ名です。 <code>F.title</code> などとして参照したり、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>/<a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>アプリ上の表示名として反映されたりします。</p> <p>例:開発用には "MyApp Dev"、本番用には "MyApp"。</p> <h4 id="applicationId--bundleId">applicationId , bundleId</h4> <p>applicationIdは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>, bundleIdは<a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>のアプリ識別子です。 通常、com.example.myapp.dev のように、環境名で末尾を変えることが多いです。</p> <h2 id="自動生成コマンドの実行">自動生成コマンドの実行</h2> <pre class="code bash" data-lang="bash" data-unlink>flutter pub run flutter_flavorizr</pre> <h1 id="最後に">最後に</h1> <p>flutter_flavorizrを利用すれば、アプリアイコンやFirebaseプロジェクトを環境ごとに用意することも可能です。ほとんど自動で必要な処理が行われるのでお手軽だと思います。<a class="keyword" href="https://d.hatena.ne.jp/keyword/IDE">IDE</a>のRun Configurationの作成もできるのでとても嬉しいなと思いました。</p> <h1 id="参考リンク">参考リンク</h1> <p><a href="https://pub.dev/packages/flutter_flavorizr"><code>flutter_flavorizr</code></a></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Flutter" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Flutter</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="excite-okazi" >excite-okazi</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/31/152105"><time data-relative datetime="2025-03-31T06:21:05Z" title="2025-03-31T06:21:05Z" class="updated">2025-03-31 15:21</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_source=blogs_entry_footer&utm_campaign=subscribe_blog"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/31/152105" data-hatena-star-title="【Flutter】flutter_flavorizrを用いて環境分けをする" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/31/152105" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/31/152105" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/31/152105"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=Flutter&text=%E3%80%90Flutter%E3%80%91flutter_flavorizr%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E7%92%B0%E5%A2%83%E5%88%86%E3%81%91%E3%82%92%E3%81%99%E3%82%8B+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F31%2F152105" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-3600 words-400 mode-markdown entry-even" id="entry-6802340630903233869" data-keyword-campaign="" data-uuid="6802340630903233869" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/25" rel="nofollow"> <time datetime="2025-03-25T06:27:45Z" title="2025-03-25T06:27:45Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">25</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/25/152745" class="entry-title-link bookmark">htmxを使ってbuttonを押した際に別のページを表示する方法</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240902/20240902135203.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>こんにちは、新卒2年目の岡崎です。今回は、htmxを使って<code>button</code>を押した際に別のページを表示する方法を紹介します。</p> <h2 id="環境">環境</h2> <ul> <li>gradle</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink>------------------------------------------------------------ Gradle <span class="synConstant">8.5</span> ------------------------------------------------------------ Build time: <span class="synConstant">2023</span>-<span class="synConstant">11</span>-<span class="synConstant">29</span> <span class="synConstant">14</span>:<span class="synConstant">08</span>:<span class="synConstant">57</span> UTC <span class="synStatement">Revision</span>: 28aca86a7180baa17117e0e5ba01d8ea9feca598 <span class="synStatement">Kotlin</span>: <span class="synConstant">1.9.20</span> <span class="synStatement">Groovy</span>: <span class="synConstant">3.0.17</span> <span class="synStatement">Ant</span>: Apache Ant(TM) version <span class="synConstant">1.10.13</span> compiled on January <span class="synConstant">4</span> <span class="synConstant">2023</span> <span class="synStatement">JVM</span>: <span class="synConstant">21.0.2</span> (Amazon.com Inc. <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS) <span class="synStatement">OS</span>: Mac OS X <span class="synConstant">12.3</span> aarch64 </pre> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a></li> </ul> <pre class="code lang-java" data-lang="java" data-unlink>openjdk version <span class="synConstant">"21.0.2"</span> <span class="synConstant">2024</span>-<span class="synConstant">01</span>-<span class="synConstant">16</span> LTS OpenJDK Runtime Environment Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS) OpenJDK <span class="synConstant">64</span>-Bit Server VM Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS, mixed mode, sharing) </pre> <ul> <li>Spring Boot</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink> . ____ _ __ _ _ /<span class="synError">\\</span> / ___'_ __ _ _(_)_ __ __ _ <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> ( ( )<span class="synError">\</span>___ | <span class="synConstant">'</span><span class="synError">_ | </span><span class="synConstant">'</span>_| | '_ <span class="synError">\</span>/ _<span class="synError">`</span> | <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\\</span>/ ___)| |_<span class="synError">)</span>| | | | | || (_| | ) <span class="synError">)</span> <span class="synError">)</span> <span class="synError">)</span> ' |____| .__|_| |_|_| |_<span class="synError">\</span>__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3<span class="synConstant">.2.1</span>) </pre> <p>今回は、htmxとBootstrapを使っているため、以下の依存関係を<code>build.gradle</code>に追加します。</p> <pre class="code lang-java" data-lang="java" data-unlink>implementation <span class="synConstant">"org.webjars.npm:htmx.org:1.9.10"</span> runtimeOnly(<span class="synConstant">"org.webjars:bootstrap:5.3.3"</span>) </pre> <h2 id="実装">実装</h2> <p>まずは、エンドポイントを作成します。<code>/demo</code>から<code>button</code>を押すと、<code>/newDemo</code>に遷移するようにするため、2つのエンドポイントを用意しました。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Controller</span> <span class="synPreProc">@RequestMapping</span>(<span class="synConstant">"/"</span>) <span class="synType">public</span> <span class="synType">class</span> DemoController { <span class="synPreProc">@GetMapping</span>(<span class="synConstant">"demo"</span>) <span class="synType">public</span> String demo() { <span class="synStatement">return</span> <span class="synConstant">"index"</span>; } <span class="synPreProc">@PostMapping</span>(<span class="synConstant">"newDemo"</span>) <span class="synType">public</span> String newDemo() { <span class="synStatement">return</span> <span class="synConstant">"newDemo"</span>; } } </pre> <p>次に、<code>resources</code>配下にHTMLファイルを作成します。</p> <ul> <li>index.html</li> </ul> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!DOCTYPE html></span> <span class="synIdentifier"><</span><span class="synStatement">html</span><span class="synIdentifier"> xmlns:th=</span><span class="synConstant">"http://www.thymeleaf.org"</span><span class="synIdentifier"> </span><span class="synType">lang</span><span class="synIdentifier">=</span><span class="synConstant">"en"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">meta</span><span class="synIdentifier"> </span><span class="synType">charset</span><span class="synIdentifier">=</span><span class="synConstant">"UTF-8"</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">meta</span><span class="synIdentifier"> </span><span class="synType">name</span><span class="synIdentifier">=</span><span class="synConstant">"viewport"</span><span class="synIdentifier"> </span><span class="synType">content</span><span class="synIdentifier">=</span><span class="synConstant">"width=device-width, initial-scale=1.0"</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">title</span><span class="synIdentifier">></span>HTMX Example<span class="synIdentifier"></</span><span class="synStatement">title</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/htmx.org/1.9.10/dist/htmx.min.js"</span><span class="synIdentifier">></</span><span class="synStatement">script</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">link</span><span class="synIdentifier"> </span><span class="synType">rel</span><span class="synIdentifier">=</span><span class="synConstant">"stylesheet"</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.css"</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">link</span><span class="synIdentifier"> </span><span class="synType">rel</span><span class="synIdentifier">=</span><span class="synConstant">"stylesheet"</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/bootstrap/5.3.3/css/bootstrap.min.css"</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"container text-center mt-5"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier">></span>demo 1<span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">button</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"btn btn-primary"</span> <span class="synIdentifier"> th:hx-post=</span><span class="synConstant">"@{/newDemo}"</span> <span class="synIdentifier"> th:hx-push-</span><span class="synType">url</span><span class="synIdentifier">=</span><span class="synConstant">"true"</span> <span class="synIdentifier"> hx-</span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"body"</span> <span class="synIdentifier"> ></span> 投稿 <span class="synIdentifier"></</span><span class="synStatement">button</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">html</span><span class="synIdentifier">></span> </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240902/20240902134601.png" width="1200" height="320" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul> <li>newDemo.html</li> </ul> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!DOCTYPE html></span> <span class="synIdentifier"><</span><span class="synStatement">html</span><span class="synIdentifier"> xmlns:th=</span><span class="synConstant">"http://www.thymeleaf.org"</span><span class="synIdentifier"> </span><span class="synType">lang</span><span class="synIdentifier">=</span><span class="synConstant">"en"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">meta</span><span class="synIdentifier"> </span><span class="synType">charset</span><span class="synIdentifier">=</span><span class="synConstant">"UTF-8"</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">meta</span><span class="synIdentifier"> </span><span class="synType">name</span><span class="synIdentifier">=</span><span class="synConstant">"viewport"</span><span class="synIdentifier"> </span><span class="synType">content</span><span class="synIdentifier">=</span><span class="synConstant">"width=device-width, initial-scale=1.0"</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">title</span><span class="synIdentifier">></span>HTMX Example<span class="synIdentifier"></</span><span class="synStatement">title</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/htmx.org/1.9.10/dist/htmx.min.js"</span><span class="synIdentifier">></</span><span class="synStatement">script</span><span class="synIdentifier">></span> <span class="synPreProc"> </span><span class="synIdentifier"><</span><span class="synStatement">link</span><span class="synIdentifier"> </span><span class="synType">rel</span><span class="synIdentifier">=</span><span class="synConstant">"stylesheet"</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">"/webjars/bootstrap/5.3.3/css/bootstrap.min.css"</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">head</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"container text-center mt-5"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">p</span><span class="synIdentifier">></span>新しいページです<span class="synIdentifier"></</span><span class="synStatement">p</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">div</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">body</span><span class="synIdentifier">></span> <span class="synIdentifier"></</span><span class="synStatement">html</span><span class="synIdentifier">></span> </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240902/20240902134629.png" width="1200" height="280" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>以下で各機能を解説します。</p> <h4 id="hx-target">hx-target</h4> <p>この属性は、どの部分を更新するかを指定します。ここでは、画面全体を更新したいので<code>body</code>を指定しています。</p> <pre class="code lang-html" data-lang="html" data-unlink>hx-target="body" </pre> <p>参考:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhtmx.org%2Fattributes%2Fhx-target%2F" title="</> htmx ~ hx-target Attribute" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://htmx.org/attributes/hx-target/">htmx.org</a></cite></p> <h4 id="hx-get">hx-get</h4> <p>指定したエンドポイントにアクセスし、現在のHTMLページの内容が入れ替わります。</p> <p><code>hx-get</code>のみでは、HTMLの内容だけが置き換わり、URLはそのままです。</p> <pre class="code lang-html" data-lang="html" data-unlink> <span class="synIdentifier"><</span><span class="synStatement">button</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"btn btn-primary"</span> <span class="synIdentifier"> th:hx-get=</span><span class="synConstant">"@{/newDemo}"</span> <span class="synIdentifier"> hx-</span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"body"</span> <span class="synIdentifier"> ></span> 投稿 <span class="synIdentifier"></</span><span class="synStatement">button</span><span class="synIdentifier">></span> </pre> <p>以下の画像に、実際の挙動を示しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240902/20240902134702.png" width="1200" height="291" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>参考:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhtmx.org%2Fattributes%2Fhx-get%2F" title="</> htmx ~ hx-get Attribute" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://htmx.org/attributes/hx-get/">htmx.org</a></cite></p> <h4 id="hx-push-url">hx-push-url</h4> <p><code>hx-push-url</code>を<code>true</code>にすると、ページの遷移に合わせてURLを変更できます。また、ブラウザの履歴にも記録できます。</p> <pre class="code lang-html" data-lang="html" data-unlink> <span class="synIdentifier"><</span><span class="synStatement">button</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"btn btn-primary"</span> <span class="synIdentifier"> th:hx-get=</span><span class="synConstant">"@{/newDemo}"</span> <span class="synIdentifier"> th:hx-push-</span><span class="synType">url</span><span class="synIdentifier">=</span><span class="synConstant">"true"</span> <span class="synIdentifier"> hx-</span><span class="synType">target</span><span class="synIdentifier">=</span><span class="synConstant">"body"</span> <span class="synIdentifier"> ></span> 投稿 <span class="synIdentifier"></</span><span class="synStatement">button</span><span class="synIdentifier">></span> </pre> <p>以下の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>で、実際の挙動を確認できます。<code>button</code>を押すと、遷移先のURLに変わりました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240902/20240902134737.png" width="1014" height="574" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ブラウザの履歴にも、遷移先のエンドポイントが追加されました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20250325/20250325152625.gif" width="1144" height="274" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>参考:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhtmx.org%2Fattributes%2Fhx-push-url%2F" title="</> htmx ~ hx-push-url Attribute" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://htmx.org/attributes/hx-push-url/">htmx.org</a></cite></p> <h2 id="最後に">最後に</h2> <p>今回は、htmxを使って<code>button</code>を押した際に別のページを表示する方法を紹介しました。少しでも参考になれば幸いです。</p> <p>また、エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>も歓迎していますので、興味があれば連絡いただければと思います。</p> <p>募集職種一覧はこちらになります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fexcite%2Fprojects" title="エキサイトホールディングス株式会社の募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/excite/projects">www.wantedly.com</a></cite></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/htmx" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">htmx</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Spring%20Boot" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Spring Boot</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="ooo-ka999" >ooo-ka999</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/25/152745"><time data-relative datetime="2025-03-25T06:27:45Z" title="2025-03-25T06:27:45Z" class="updated">2025-03-25 15:27</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_campaign=subscribe_blog&utm_source=blogs_entry_footer"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/25/152745" data-hatena-star-title="htmxを使ってbuttonを押した際に別のページを表示する方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/25/152745" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/25/152745" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/25/152745"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=htmx&hashtags=Spring+Boot&text=htmx%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6button%E3%82%92%E6%8A%BC%E3%81%97%E3%81%9F%E9%9A%9B%E3%81%AB%E5%88%A5%E3%81%AE%E3%83%9A%E3%83%BC%E3%82%B8%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F25%2F152745" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-last autopagerize_page_element chars-1200 words-100 mode-markdown entry-odd" id="entry-6802418398337158746" data-keyword-campaign="" data-uuid="6802418398337158746" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date last"> <a href="https://tech.excite.co.jp/archive/2025/03/25" rel="nofollow"> <time datetime="2025-03-25T00:49:01Z" title="2025-03-25T00:49:01Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">25</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/25/094901" class="entry-title-link bookmark">PHPで特殊文字をエスケープする方法</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20250317/20250317102754.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul class="table-of-contents"> <li><a href="#PHPで特殊文字をエスケープする方法">PHPで特殊文字をエスケープする方法</a></li> <li><a href="#なぜ特殊文字をエスケープする必要があるのか">なぜ特殊文字をエスケープする必要があるのか</a></li> <li><a href="#PHPで特殊文字をエスケープする方法-1">PHPで特殊文字をエスケープする方法</a><ul> <li><a href="#コードの解説">コードの解説</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="PHPで特殊文字をエスケープする方法"><a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする方法</h1> <p>初めまして、新卒2年目の岡崎です。今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>でHTMLタグなどに使われる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする方法を紹介します。</p> <h1 id="なぜ特殊文字をエスケープする必要があるのか">なぜ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする必要があるのか</h1> <p>プログラミングでは、特定の文字(<code><</code>、<code>></code>、<code>&</code>、<code>"</code>、<code>'</code>等)が特別な意味を持つため、そのまま使用すると問題が発生する可能性があります。</p> <p>例えば、HTMLや<a class="keyword" href="https://d.hatena.ne.jp/keyword/SQL">SQL</a>では<code>"</code>や<code>'</code>はデータの境界を示し、<code><script></code>はブラウザで実行されるコードとして解釈される可能性があります。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープすることで、これらの文字を「単なる文字」として扱い、意図しない動作やセキュリティリスクを防ぐことができます。</p> <h1 id="PHPで特殊文字をエスケープする方法-1"><a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする方法</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする場合、<code>htmlspecialchars</code>関数を使います。<code>htmlspecialchars</code>関数は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a> 4以降で使えます。</p> <pre class="code lang-php" data-lang="php" data-unlink>$input = '<span class="synIdentifier"><</span><span class="synStatement">script</span><span class="synIdentifier">></span><span class="synStatement">alert</span><span class="synSpecial">(</span><span class="synConstant">"test"</span><span class="synSpecial">)</span><span class="synStatement">;</span><span class="synIdentifier"></</span><span class="synStatement">script</span><span class="synIdentifier">></span>'; echo htmlspecialchars($input, ENT_QUOTES, "UTF-8"); </pre> <p>出力結果</p> <pre class="code" data-lang="" data-unlink>&lt;script&gt;alert(&quot;test&quot;);&lt;/script&gt;</pre> <h2 id="コードの解説">コードの解説</h2> <p><code>htmlspecialchars</code>関数は、HTMLで特別な意味を持つ文字を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープし、表示できるように変換します。</p> <ul> <li>第一引数:<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープしたい文字列</li> <li>第二引数:<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープの対象となる文字を指定するフラグ</li> <li>第三引数:<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>(通常は "<a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a>" を指定)</li> </ul> <p>第二引数でよく使われる入るフラグを紹介します。</p> <table> <thead> <tr> <th> フラグ </th> <th> 説明 </th> </tr> </thead> <tbody> <tr> <td> <code>ENT_QUOTES</code> </td> <td> ダブルクォート (<code>"</code>) は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープするが、シングルクォート (<code>'</code>) はそのまま </td> </tr> <tr> <td> <code>ENT_NOQUOTES</code> </td> <td> クォートを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープしない </td> </tr> <tr> <td> <code>ENT_SUBSTITUTE</code> </td> <td> 文字<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%B3%A1%BC%A5%C7%A5%A3%A5%F3%A5%B0">エンコーディング</a>(<a class="keyword" href="https://d.hatena.ne.jp/keyword/UTF-8">UTF-8</a> など)に存在しない文字を<code>?</code>に置き換える </td> </tr> <tr> <td> <code>ENT_HTML401</code> </td> <td> HTML4.01のエンティティ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%B3%A1%BC%A5%C9">エンコード</a>ルールに従う </td> </tr> </tbody> </table> <p><code>htmlspecialchars</code>関数の公式ドキュメントはこちらです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.php.net%2Fmanual%2Fja%2Ffunction.htmlspecialchars.php" title="PHP: htmlspecialchars - Manual" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.php.net/manual/ja/function.htmlspecialchars.php">www.php.net</a></cite></p> <h1 id="最後に">最後に</h1> <p>今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP">PHP</a>でHTMLタグ等に使われる<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%C3%BC%EC%CA%B8%BB%FA">特殊文字</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープする方法を紹介しました。適切に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ケープを行うことで、予期しない動作を防いだり、セキュリティ対策をすることができます。</p> <p>皆さんの何かのお役に立てれば幸いです。</p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/PHP" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">PHP</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="ooo-ka999" >ooo-ka999</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/25/094901"><time data-relative datetime="2025-03-25T00:49:01Z" title="2025-03-25T00:49:01Z" class="updated">2025-03-25 09:49</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/25/094901" data-hatena-star-title="PHPで特殊文字をエスケープする方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/25/094901" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/25/094901" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/25/094901"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=PHP&text=PHP%E3%81%A7%E7%89%B9%E6%AE%8A%E6%96%87%E5%AD%97%E3%82%92%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F25%2F094901" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-3200 words-200 mode-markdown entry-even" id="entry-6802418398337171851" data-keyword-campaign="" data-uuid="6802418398337171851" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/24" rel="nofollow"> <time datetime="2025-03-24T01:07:41Z" title="2025-03-24T01:07:41Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">24</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/24/100741" class="entry-title-link bookmark">【Flutter】Chopperを用いてGitHubのAPIから検索結果を取得してみる</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/Flutter" class="entry-category-link category-Flutter">Flutter</a> </div> </header> <div class="entry-content hatenablog-entry"> <p>こんにちは。エキサイトでアプリエンジニアをしている岡島です。今回は業務でChopperというライブラリを使用したので、Chopperの基礎を説明しながら、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>で検索結果を取得するまでを記事にしていこうと思います。<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>の仕様については省略します。</p> <ul class="table-of-contents"> <li><a href="#Chopperとは">Chopperとは</a></li> <li><a href="#環境">環境</a></li> <li><a href="#APIサービスを定義する">APIサービスを定義する</a><ul> <li><a href="#ChopperApibaseUrl-searchrepositories">@ChopperApi(baseUrl: '/search/repositories')</a></li> <li><a href="#GETアノテーション">@GET()アノテーション</a></li> <li><a href="#クエリパラメータの設定">クエリパラメータの設定</a></li> </ul> </li> <li><a href="#APIクライアントの初期化">APIクライアントの初期化</a><ul> <li><a href="#baseUrlについて">baseUrlについて</a></li> <li><a href="#services-GithubServicecreateについて">services: [GithubService.create()]について</a></li> <li><a href="#converter-const-JsonConverter">converter: const JsonConverter()</a></li> </ul> </li> <li><a href="#検索結果を表示">検索結果を表示</a></li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#使用ライブラリ">使用ライブラリ</a></li> </ul> <h1 id="Chopperとは">Chopperとは</h1> <p>Chopperは Retrofit(Kotlin/<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>のHTTPクライアント)のように、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>を使って<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を定義できます。Chopper では、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a> 通信のコードを自動生成 されるので、URL や HTTP メソッド、パラメータを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>を用いて直感的に定義できる のが特徴です。</p> <h1 id="環境">環境</h1> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">dependencies</span><span class="synSpecial">:</span> <span class="synIdentifier">chopper</span><span class="synSpecial">:</span> ^8.1.0 <span class="synIdentifier">dev_dependencies</span><span class="synSpecial">:</span> <span class="synIdentifier">build_runner</span><span class="synSpecial">:</span> ^2.4.9 <span class="synIdentifier">chopper_generator</span><span class="synSpecial">:</span> ^8.1.0 </pre> <h1 id="APIサービスを定義する"><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>サービスを定義する</h1> <p><code>lib/github_service.dart</code>ファイルを作成し、以下のコードを追加します。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synStatement">import</span> <span class="synConstant">'package:chopper/chopper.dart'</span>; <span class="synIdentifier">part</span> <span class="synConstant">'github_service.chopper.dart'</span>; @ChopperApi(baseUrl: <span class="synConstant">'/search/repositories'</span>) <span class="synIdentifier">abstract</span> <span class="synStatement">class</span> GithubService <span class="synStatement">extends</span> ChopperService { @GET() Future<Response> searchFlutterRepos( @Query(<span class="synConstant">'q'</span>) <span class="synType">String</span> query, @Query(<span class="synConstant">'per_page'</span>) <span class="synType">int</span> perPage, ); <span class="synIdentifier">static</span> GithubService create([ChopperClient? client]) => _$GithubService(client); } </pre> <p><code>github_service.dart</code>を追加したら、 ターミナルで以下のコマンドを実行し、Chopperのコードを生成します。</p> <pre class="code bash" data-lang="bash" data-unlink>dart run build_runner build</pre> <h3 id="ChopperApibaseUrl-searchrepositories">@ChopperApi(baseUrl: '/search/repositories')</h3> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>検索エンドポイントである <code>/search/repositories</code>を設定しています。</p> <p>後述するChopperClientのbaseUrlを<code>https://api.github.com</code>に設定すると、</p> <pre class="code" data-lang="" data-unlink>https://api.github.com/search/repositories</pre> <p>にリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが送信されます。</p> <h3 id="GETアノテーション">@GET()<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a></h3> <p>@GET()<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%CE%A5%C6%A1%BC%A5%B7%A5%E7%A5%F3">アノテーション</a>を付与することで、このメソッドがGETリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送信する<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>メソッドになります。 @GET, @POST, @PUT, @PATCH, @DELETE, @HEADが用意されています。</p> <h3 id="クエリパラメータの設定">クエリパラメータの設定</h3> <p><code>@Query('q')</code>と記述すると、クエリパラメータの設定ができます。今回の例では、<code>query</code>という変数を<code>q</code>というパラメータ名として設定しています。</p> <h1 id="APIクライアントの初期化"><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>クライアントの初期化</h1> <p>続いてクライアントの初期化を行います。</p> <pre class="code lang-dart" data-lang="dart" data-unlink> <span class="synStatement">final</span> client = ChopperClient( baseUrl: Uri.parse(<span class="synConstant">'https://api.github.com'</span>), services: [GithubService.create()], converter: <span class="synStatement">const</span> JsonConverter(), ); <span class="synStatement">final</span> service = GithubService.create(client); </pre> <h3 id="baseUrlについて">baseUrlについて</h3> <p><code>baseUrl</code>は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>通信の際にリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送る「基準となるURL」を設定します。 この場合、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>の基本URLである<code>https://api.github.com</code> を指定しています。</p> <p>共通の<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>のベースURLはChopperClientで設定するのが良さそうです。</p> <h3 id="services-GithubServicecreateについて">services: [GithubService.create()]について</h3> <p><code>services:</code>で指定すると、指定した<code>GithubService.create()</code>が使用できるようになります。</p> <p><code>services:</code>で<code>GithubService.create()</code>を登録することで、以下のようにsearchFlutterReposメソッドを呼び出すことができます。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synStatement">final</span> service = GithubService.create(client); <span class="synStatement">final</span> response = <span class="synStatement">await</span> service.searchFlutterRepos(<span class="synConstant">'flutter'</span>, 10); </pre> <h3 id="converter-const-JsonConverter">converter: const JsonConverter()</h3> <p>converterのオプションは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a> のレスポンス(受信したデータ)を適切なフォーマットに変換するために使用されます。 <code>JsonConverter</code>を指定すると、Chopperが自動的に<a class="keyword" href="https://d.hatena.ne.jp/keyword/JSON">JSON</a>をパースしてくれます。</p> <h1 id="検索結果を表示">検索結果を表示</h1> <p>以下に<code>main.dart</code>の全コードを載せておきます。</p> <pre class="code lang-dart" data-lang="dart" data-unlink><span class="synType">void</span> main() <span class="synStatement">async</span> { <span class="synStatement">final</span> client = ChopperClient( baseUrl: Uri.parse(<span class="synConstant">'https://api.github.com'</span>), services: [GithubService.create()], converter: <span class="synStatement">const</span> JsonConverter(), ); <span class="synStatement">final</span> service = GithubService.create(client); <span class="synStatement">final</span> response = <span class="synStatement">await</span> service.searchFlutterRepos(<span class="synConstant">'flutter'</span>, 10); <span class="synStatement">if</span> (response.isSuccessful) { print(response.body); } <span class="synStatement">else</span> { print(<span class="synConstant">'Error: ${response.statusCode}'</span>); } } </pre> <h1 id="まとめ">まとめ</h1> <p>今回はChopperの基礎について<a class="keyword" href="https://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を触りながら説明しました。この記事が誰かのお役に立てれば幸いです。</p> <h1 id="使用ライブラリ">使用ライブラリ</h1> <p><a href="https://pub.dev/packages/chopper">https://pub.dev/packages/chopper</a></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Flutter" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Flutter</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/chopper" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">chopper</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="excite-okazi" >excite-okazi</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/24/100741"><time data-relative datetime="2025-03-24T01:07:41Z" title="2025-03-24T01:07:41Z" class="updated">2025-03-24 10:07</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/24/100741" data-hatena-star-title="【Flutter】Chopperを用いてGitHubのAPIから検索結果を取得してみる" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/24/100741" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/24/100741" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/24/100741"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=Flutter&hashtags=chopper&text=%E3%80%90Flutter%E3%80%91Chopper%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6GitHub%E3%81%AEAPI%E3%81%8B%E3%82%89%E6%A4%9C%E7%B4%A2%E7%B5%90%E6%9E%9C%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F24%2F100741" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-last autopagerize_page_element chars-2400 words-200 mode-markdown entry-odd" id="entry-6802418398338936598" data-keyword-campaign="" data-uuid="6802418398338936598" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date last"> <a href="https://tech.excite.co.jp/archive/2025/03/24" rel="nofollow"> <time datetime="2025-03-24T00:43:42Z" title="2025-03-24T00:43:42Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">24</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/24/094342" class="entry-title-link bookmark">Tailwind CSSに標準搭載されているリセットCSSを無効にする方法</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/Tailwind%20CSS" class="entry-category-link category-Tailwind-CSS">Tailwind CSS</a> <a href="https://tech.excite.co.jp/archive/category/CSS" class="entry-category-link category-CSS">CSS</a> <a href="https://tech.excite.co.jp/archive/category/%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0" class="entry-category-link category-スタイリング">スタイリング</a> <a href="https://tech.excite.co.jp/archive/category/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89" class="entry-category-link category-フロントエンド">フロントエンド</a> </div> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saitoayumu/20250324/20250324075037.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。エキサイトでデザイナーをしている齋藤です。</p> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>に標準搭載されているリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を無効にする方法をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#実現したいこと">実現したいこと</a></li> <li><a href="#Preflightを無効にする方法">Preflightを無効にする方法</a></li> <li><a href="#独自のリセットCSSを設定する">独自のリセットCSSを設定する</a></li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#参考文献">参考文献</a></li> </ul> </li> </ul> <h2 id="実現したいこと">実現したいこと</h2> <p>冒頭、実現したいことを整理します。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>ではリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>として、<a href="https://tailwindcss.com/docs/preflight">Preflightが標準搭載</a>されています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2Fdocs%2Fpreflight" title="Preflight - Base styles" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/docs/preflight">tailwindcss.com</a></cite></p> <p>Preflightは<code>@import "tailwindcss";</code>に含まれ、<code>base</code>レイヤーに組み込まれるようになっています。</p> <p>なお、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>のクラスは<code>@layer</code>を用いて順位付けされており、</p> <ol> <li><code>theme</code> レイヤー</li> <li><code>base</code>レイヤー</li> <li><code>components</code>レイヤー</li> <li><code>utilities</code>レイヤー</li> </ol> <p>の順で上書きされていきます。(<code>p-*</code>などのユーティリティクラスが最優先となる)</p> <p>Preflightはブラウザ依存の振る舞いを取り除いて、ブラウザ間の表現の不一致を防ぐ役割を担います。</p> <p>一方で、すでにプロジェクト内でリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を採用している場合に、Preflightによって意図しない描写を招くことがあります。</p> <p>今回実現したいのは、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>に標準搭載されているリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>(Preflight)を無効にして、独自のリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>に置き換えることです。</p> <h2 id="Preflightを無効にする方法">Preflightを無効にする方法</h2> <p>結論から申し上げると、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>のインポートを書き換えることでPreflightを無効にできます。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を使用する場合、設定ファイルの<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>で <code>@import "tailwindcss";</code>を宣言します。</p> <p><code>@import "tailwindcss";</code>には以下のような<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>が含まれています。</p> <pre class="code lang-css" data-lang="css" data-unlink>@layer theme<span class="synSpecial">,</span> <span class="synStatement">base</span><span class="synSpecial">,</span> components<span class="synSpecial">,</span> utilities; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/theme.css"</span><span class="synPreProc"> layer(theme)</span>; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/preflight.css"</span><span class="synPreProc"> layer(base)</span>; // これがPreflight <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/utilities.css"</span><span class="synPreProc"> layer(utilities)</span>; </pre> <p><code>@import "tailwindcss/preflight.css" layer(base);</code>が読み込まれないようにするには、<code>@import "tailwindcss";</code>を用いずに次のように各種<code>import</code>宣言を直に記述します。</p> <pre class="code lang-css" data-lang="css" data-unlink>@layer theme<span class="synSpecial">,</span> <span class="synStatement">base</span><span class="synSpecial">,</span> components<span class="synSpecial">,</span> utilities; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/theme.css"</span><span class="synPreProc"> layer(theme)</span>; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/utilities.css"</span><span class="synPreProc"> layer(utilities)</span>; </pre> <p>これで、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>のセットからPreflightのみを取り除けます。</p> <h2 id="独自のリセットCSSを設定する">独自のリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を設定する</h2> <p>独自のリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>、すなわちタグに対するデフォルトのスタイルを設定したい場合は、<code>base</code>レイヤー内に記述します。</p> <p>例えば、「<code>h1</code>タグの文字色は赤色」というデフォルトスタイルを設定する場合は、以下のようにします。</p> <pre class="code lang-css" data-lang="css" data-unlink>@layer theme<span class="synSpecial">,</span> <span class="synStatement">base</span><span class="synSpecial">,</span> components<span class="synSpecial">,</span> utilities; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/theme.css"</span><span class="synPreProc"> layer(theme)</span>; <span class="synPreProc">@import </span><span class="synConstant">"tailwindcss/utilities.css"</span><span class="synPreProc"> layer(utilities)</span>; @layer <span class="synStatement">base</span> <span class="synIdentifier">{</span> <span class="synStatement">h1</span> <span class="synIdentifier">{</span> <span class="synType">color</span>: <span class="synConstant">red</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> </pre> <p>これで、<code>h1</code>タグの文字色は赤色になります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!--- デフォルトで文字色は赤色になる --</span><span class="synError">-</span><span class="synComment">></span> <span class="synIdentifier"><</span><span class="synStatement">h1</span><span class="synIdentifier">></span>見出し<span class="synIdentifier"></</span><span class="synStatement">h1</span><span class="synIdentifier">></span> </pre> <p>冒頭に申し上げた通り、<code>base</code>レイヤーの優先順位は低いため、ユーティリティクラスを付与すると上書きされます。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!--- ユーティリティクラスが最優先になるため、文字色は黒色になる --</span><span class="synError">-</span><span class="synComment">></span> <span class="synIdentifier"><</span><span class="synStatement">h1</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"text-black"</span><span class="synIdentifier">></span>見出し<span class="synIdentifier"></</span><span class="synStatement">h1</span><span class="synIdentifier">></span> </pre> <h2 id="まとめ">まとめ</h2> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>に標準搭載されているリセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>(Preflight)を無効にする方法をご紹介しました。</p> <p>既存のプロジェクトにTailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を乗せる場合に、リセット<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>は据え置きにしたいモチベーションが生まれることがあるかと思います。</p> <p>ただし、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>のユーティリティクラスはPreflightをベースに設計されていますので、その点は注意が必要です。</p> <p>まっさらなプロジェクトを筆頭に、基本的にはPreflightをそのまま使用することをおすすめします。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を使用される方の一助となれば幸いです。ご精読ありがとうございました。</p> <h3 id="参考文献">参考文献</h3> <ul> <li><a href="https://tailwindcss.com/docs/preflight#disabling-preflight">Preflight - Base styles - Tailwind CSS</a></li> </ul> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="saitoayumu" >saitoayumu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/24/094342"><time data-relative datetime="2025-03-24T00:43:42Z" title="2025-03-24T00:43:42Z" class="updated">2025-03-24 09:43</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_campaign=subscribe_blog&utm_source=blogs_entry_footer"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/24/094342" data-hatena-star-title="Tailwind CSSに標準搭載されているリセットCSSを無効にする方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/24/094342" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/24/094342" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/24/094342"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?text=Tailwind+CSS%E3%81%AB%E6%A8%99%E6%BA%96%E6%90%AD%E8%BC%89%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88CSS%E3%82%92%E7%84%A1%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F24%2F094342" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-4400 words-400 mode-markdown entry-even" id="entry-6802418398336576797" data-keyword-campaign="" data-uuid="6802418398336576797" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/20" rel="nofollow"> <time datetime="2025-03-20T09:07:26Z" title="2025-03-20T09:07:26Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">20</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/20/180726" class="entry-title-link bookmark">SpringBootのBeanスコープを@Scopeで使い分ける</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p>エキサイト株式会社エンジニアの佐々木です。SpringBootのBeanスコープのデフォルトはSingletonですが、@Scopeを利用して使い分けていこうと思います。</p> <ul class="table-of-contents"> <li><a href="#Beanスコープとは">Beanスコープとは?</a><ul> <li><a href="#singleton">singleton</a></li> <li><a href="#request">request</a></li> <li><a href="#session">session</a></li> <li><a href="#prototype">prototype</a></li> </ul> </li> <li><a href="#singletonスコープprototypeスコープの違い">singletonスコープ、prototypeスコープの違い</a><ul> <li><a href="#singletonスコープ">singletonスコープ</a></li> <li><a href="#requestスコープ">requestスコープ</a></li> <li><a href="#prototypeスコープ">prototypeスコープ</a></li> <li><a href="#補足ApplicationContextについて">補足:ApplicationContextについて</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h2 id="Beanスコープとは">Beanスコープとは?</h2> <p>SpringBootはDIコンテナを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CA%DD%CD%AD">保有</a>していて、起動時に多くのBeanを生成します。そのBeanがどのように生成され、どこで再利用されるかを@Scopeで制御できます。</p> <h3 id="singleton">singleton</h3> <p>Beanのデフォルトのスコープになります。DIコンテナの中で最初の1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>しか作られません。1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をアプリケーション全体で利用するのでメモリ消費は少なくパフォーマンスがいいですが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>フィールドは、マルチスレッドでの利用は注意する必要があります。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synComment">// デフォルトsingleton</span> <span class="synPreProc">@Component</span> <span class="synType">class</span> SingletonData { <span class="synIdentifier">...</span> } </pre> <h3 id="request">request</h3> <p>リク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トごとに1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が作られます。1つのリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト内で共有してもいいようなデータの管理に使用します。</p> <pre class="code lang-java" data-lang="java" data-unlink> <span class="synPreProc">@Component</span> <span class="synPreProc">@RequestScope</span> <span class="synType">class</span> ReuestData { <span class="synIdentifier">...</span> } </pre> <h3 id="session">session</h3> <p>HTTPSessionごとに1つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が作られます。SpringSessionを利用し、同一のセッション内で共有してもいいようなデータの管理に使用します。</p> <pre class="code lang-java" data-lang="java" data-unlink> <span class="synPreProc">@Component</span> <span class="synPreProc">@SessionScope</span> <span class="synType">class</span> SessionData { <span class="synIdentifier">...</span> } </pre> <h3 id="prototype">prototype</h3> <p>DIコンテナから取り出される毎に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が生成されるスコープになります。Listの中に含まれてるようなものに使用します。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Component</span> <span class="synPreProc">@Scope</span>(<span class="synConstant">"prototype"</span>) <span class="synType">class</span> ProtoTypeData { <span class="synIdentifier">...</span> } </pre> <h2 id="singletonスコープprototypeスコープの違い">singletonスコープ、prototypeスコープの違い</h2> <p>今回はsingletonスコープ、prototypeスコープ、requestスコープの検証を行います。(sessionスコープは、SpringSessionの導入などがあり煩雑なので割愛します)</p> <p>今回は、ApplicationContextを使用したDIの方法で検証します。</p> <h3 id="singletonスコープ">singletonスコープ</h3> <p>singletonスコープのデータクラスは下記になります。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Component</span> <span class="synPreProc">@Data</span> <span class="synType">class</span> SingletonScopeData { <span class="synType">private</span> <span class="synType">int</span> id = <span class="synStatement">new</span> SecureRandom().nextInt(); <span class="synType">private</span> String uuid = UUID.randomUUID().toString(); } </pre> <p>テストコードは下記になります。ApplicationContextを使用して、2回SingletonScopeDataを呼び出していますが、同じ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が返っています。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@SpringBootTest</span>(classes = {SingletonScopeData.<span class="synType">class</span>}) <span class="synType">class</span> SingletonScopeDataTest { <span class="synPreProc">@Autowired</span> <span class="synType">private</span> ApplicationContext applicationContext; <span class="synPreProc">@Test</span> <span class="synType">void</span> testSingleton() { SingletonScopeData bean1 = applicationContext.getBean(SingletonScopeData.<span class="synType">class</span>); SingletonScopeData bean2 = applicationContext.getBean(SingletonScopeData.<span class="synType">class</span>); Assertions.assertSame(bean1, bean2); <span class="synComment">// true</span> } } </pre> <h3 id="requestスコープ">requestスコープ</h3> <p>requestスコープのデータクラスは下記になります。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Component</span> <span class="synPreProc">@Data</span> <span class="synPreProc">@RequestScope</span> <span class="synType">class</span> RequestScopeData { <span class="synType">private</span> <span class="synType">int</span> id = <span class="synStatement">new</span> SecureRandom().nextInt(); <span class="synType">private</span> String uuid = UUID.randomUUID().toString(); } </pre> <p>リク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト単位で<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が作成されますので、下記のようなコントローラーを用意して、このコントローラーに対してテストコードを書きます。</p> <pre class="code lang-java" data-lang="java" data-unlink> <span class="synPreProc">@RestController</span> <span class="synPreProc">@RequestMapping</span>(<span class="synConstant">"scope"</span>) <span class="synPreProc">@Slf4j</span> <span class="synType">public</span> <span class="synType">class</span> ScopeController { <span class="synPreProc">@Autowired</span> <span class="synType">private</span> RequestScopeData requestScopeData; <span class="synPreProc">@GetMapping</span>(<span class="synConstant">"request"</span>) <span class="synType">public</span> String requestScode() { <span class="synStatement">return</span> requestScopeData.toString(); } } </pre> <p>テストコードは下記になります。1回目のリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トと2回目のリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トで異なる値が返却されています。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@WebMvcTest</span>(controllers = ScopeController.<span class="synType">class</span>) <span class="synPreProc">@Import</span>(RequestScopeData.<span class="synType">class</span>) <span class="synPreProc">@Slf4j</span> <span class="synType">class</span> ScopeControllerTest { <span class="synPreProc">@Autowired</span> <span class="synType">private</span> MockMvc mockMvc; <span class="synPreProc">@Test</span> <span class="synType">void</span> testRequest() <span class="synType">throws</span> Exception { String result1 = mockMvc.perform( get(<span class="synConstant">"/scope/request"</span>)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); String result2 = mockMvc.perform( get(<span class="synConstant">"/scope/request"</span>)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); log.info(<span class="synConstant">"result1: {}"</span>, result1); <span class="synComment">// RequestScopeData(id=-1265028686, name=555946a3-9dbf-4833-9d73-a41c3474bcf5)</span> log.info(<span class="synConstant">"result2: {}"</span>, result2); <span class="synComment">// RequestScopeData(id=415725629, name=9fe5ad43-6a22-43b1-89f8-272077d1f448)</span> Assertions.assertNotEquals(result1,result2); <span class="synComment">// true</span> } } </pre> <h3 id="prototypeスコープ">prototypeスコープ</h3> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Component</span> <span class="synPreProc">@Data</span> <span class="synPreProc">@Scope</span>(<span class="synConstant">"prototype"</span>) <span class="synType">class</span> PrototypeScopeData { <span class="synType">private</span> <span class="synType">int</span> id = <span class="synStatement">new</span> SecureRandom().nextInt(); <span class="synType">private</span> String uuid = UUID.randomUUID().toString(); } </pre> <p>テストコードは下記になります。PrototypeScopeを2回呼び出して比較をしていますが、同一<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>でもなく、値も異なります。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@SpringBootTest</span>(classes = {PrototypeScopeData.<span class="synType">class</span>}) <span class="synType">class</span> PrototypeScopeDataTest { <span class="synPreProc">@Autowired</span> <span class="synType">private</span> ApplicationContext applicationContext; <span class="synPreProc">@Test</span> <span class="synType">void</span> testPrototype() { PrototypeScopeData bean1 = applicationContext.getBean(PrototypeScopeData.<span class="synType">class</span>); PrototypeScopeData bean2 = applicationContext.getBean(PrototypeScopeData.<span class="synType">class</span>); assertNotEquals(bean1, bean2); <span class="synComment">// true</span> assertNotSame(bean1, bean2); <span class="synComment">// true</span> } } </pre> <h3 id="補足ApplicationContextについて">補足:ApplicationContextについて</h3> <p>ApplicationContextは、Springの中のDIコンテナのコア部分で、Beanライフサイクル(オブジェクトの生成、破棄)を管理しているクラスになります。データクラスや<a class="keyword" href="https://d.hatena.ne.jp/keyword/DTO">DTO</a>などで<code>prototype</code>スコープで利用する場合に便利です。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@SpringBootTest</span>(classes = {PrototypeScopeData.<span class="synType">class</span>}) <span class="synType">class</span> PrototypeScopeDataTest { <span class="synPreProc">@Autowired</span> <span class="synType">private</span> ApplicationContext applicationContext; <span class="synPreProc">@Test</span> <span class="synType">void</span> testPrototype() { <span class="synComment">// prototypeの場合は、新しいインスタンスが返却される</span> PrototypeScopeData prototype = applicationContext.getBean(PrototypeScopeData.<span class="synType">class</span>); <span class="synComment">// singletonの場合は、すでにインスタンス化されたオブジェクトが返却される</span> SingletonScopeData singleton = applicationContext.getBean(SingletonScopeData.<span class="synType">class</span>); } } </pre> <h2 id="まとめ">まとめ</h2> <p>Beanスコープを理解しながら使えば、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C3%B1%C2%CE%A5%C6%A5%B9%A5%C8">単体テスト</a>が書きやすくより保守性の高いコードがかけると思います。 staticメソッドなどの濫用も防げます。検索してもあんまりでてこなかったので、簡単にですがまとめておきます。</p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Java" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Java</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/spring" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">spring</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="earu" >earu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/20/180726"><time data-relative datetime="2025-03-20T09:07:26Z" title="2025-03-20T09:07:26Z" class="updated">2025-03-20 18:07</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_source=blogs_entry_footer&utm_campaign=subscribe_blog"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/20/180726" data-hatena-star-title="SpringBootのBeanスコープを@Scopeで使い分ける" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/20/180726" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/20/180726" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/20/180726"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=Java&hashtags=spring&text=SpringBoot%E3%81%AEBean%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%82%92%40Scope%E3%81%A7%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91%E3%82%8B+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F20%2F180726" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-1600 words-100 mode-markdown entry-odd" id="entry-6802418398337137065" data-keyword-campaign="" data-uuid="6802418398337137065" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/17" rel="nofollow"> <time datetime="2025-03-17T00:40:55Z" title="2025-03-17T00:40:55Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">17</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/17/094055" class="entry-title-link bookmark">Google Ad Manager(GAM)でサードパーティクリエイティブのクリック数が取得できない場合の対処法</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A2%E3%83%89%E3%83%86%E3%82%AF%E3%83%8E%E3%83%AD%E3%82%B8%E3%83%BC" class="entry-category-link category-アドテクノロジー">アドテクノロジー</a> <a href="https://tech.excite.co.jp/archive/category/Google%20Ad%20Manager" class="entry-category-link category-Google-Ad-Manager">Google Ad Manager</a> </div> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saitoayumu/20250317/20250317082218.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。エキサイトでデザイナーをしている齋藤です。</p> <p>今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> Ad Managerで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>クリエイティブのクリック数が取得できない場合の対処法をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#前提">前提</a></li> <li><a href="#実現したいこと">実現したいこと</a></li> <li><a href="#クリックをトランキングするにはマクロの挿入が必要">クリックをトランキングするにはマクロの挿入が必要</a></li> <li><a href="#クリックがトラッキングできているかチェックする">クリックがトラッキングできているかチェックする</a></li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#参考文献">参考文献</a></li> </ul> </li> </ul> <h2 id="前提">前提</h2> <p>冒頭、前提を整理します。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> Ad Manager(以下、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GAM">GAM</a>)は、サイトに対して広告の配信をしたり収益の管理ができるツールです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/GAM">GAM</a>では配信する広告の内容、例えばリンク付きのバナーなどは「クリエイティブ」と呼ばれます。</p> <p>クリエイティブの種類の一つである「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>」を使用して、以下のようなHTMLを配信したいとします。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">a</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">"https://example.com/"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">img</span><span class="synIdentifier"> </span><span class="synType">alt</span><span class="synIdentifier">=</span><span class="synConstant">"..."</span><span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">"..."</span><span class="synIdentifier"> /></span> <span class="synIdentifier"></</span><span class="synStatement">a</span><span class="synIdentifier">></span> </pre> <h2 id="実現したいこと">実現したいこと</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/GAM">GAM</a>ではクリエイティブのインプレッションやクリック数が確認できます。</p> <p>今回実現したいのは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>クリエイティブのクリック数を取得することです。</p> <h2 id="クリックをトランキングするにはマクロの挿入が必要">クリックをトランキングするにはマクロの挿入が必要</h2> <p>先にお示ししたHTMLのままでは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/GAM">GAM</a>がクリックをトランキングできずに、実際にはクリックされていてもクリック数が0になってしまいます。</p> <p><code>a</code>タグを用いて遷移させる場合に、クリック(リンクを踏んだか)をトランキングするにはマクロの挿入が必要です。</p> <p>具体的には<a href="https://support.google.com/admanager/answer/177328?hl=ja">GAMのヘルプページ</a>でも示されている通り、クリエイティブを設定する際に<code>href</code>属性に指定したURLの先頭にクリックマクロである<code>%%CLICK_URL_UNESC%%</code>を記載します。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fadmanager%2Fanswer%2F177328%3Fhl%3Dja" title="クリエイティブでクリックがトラッキングされない - Google アド マネージャー ヘルプ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.google.com/admanager/answer/177328?hl=ja">support.google.com</a></cite></p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synComment"><!-- 遷移先URLの先頭にマクロを追加する --></span> <span class="synIdentifier"><</span><span class="synStatement">a</span><span class="synIdentifier"> </span><span class="synType">href</span><span class="synIdentifier">=</span><span class="synConstant">"%%CLICK_URL_UNESC%%https://example.com/"</span><span class="synIdentifier">></span> <span class="synIdentifier"><</span><span class="synStatement">img</span><span class="synIdentifier"> </span><span class="synType">alt</span><span class="synIdentifier">=</span><span class="synConstant">"..."</span><span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">"..."</span><span class="synIdentifier"> /></span> <span class="synIdentifier"></</span><span class="synStatement">a</span><span class="synIdentifier">></span> </pre> <h2 id="クリックがトラッキングできているかチェックする">クリックがト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0">ラッキング</a>できているかチェックする</h2> <p>クリックがト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0">ラッキング</a>できているかチェックするには、クリエイティブのプレビュー画面で設定したバナーをクリックします。</p> <p>次のようなページに遷移すればト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0">ラッキング</a>できています。</p> <p><figure class="figure-image figure-image-fotolife" title="クリックのトラッキングが成功している場合に表示されるページ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saitoayumu/20250317/20250317081554.png" width="1200" height="759" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クリックのト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%C3%A5%AD%A5%F3%A5%B0">ラッキング</a>が成功している場合に表示されるページ</figcaption></figure></p> <h2 id="まとめ">まとめ</h2> <p>今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> Ad Managerで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%C9%A5%D1%A1%BC%A5%C6%A5%A3">サードパーティ</a>クリエイティブのクリック数が取得できない場合の対処法をご紹介しました。</p> <p>クリック数が取得できない事態は、私が<a class="keyword" href="https://d.hatena.ne.jp/keyword/GAM">GAM</a>使いはじめた時に実際に遭遇した現象です。</p> <p>本稿が、同じ現象に遭遇している方の一助となれば幸いです。</p> <p>ご精読ありがとうございました。</p> <h3 id="参考文献">参考文献</h3> <ul> <li><a href="https://support.google.com/admanager/answer/177328?hl=ja">クリエイティブでクリックがトラッキングされない - Google アド マネージャー ヘルプ</a></li> </ul> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="saitoayumu" >saitoayumu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/17/094055"><time data-relative datetime="2025-03-17T00:40:55Z" title="2025-03-17T00:40:55Z" class="updated">2025-03-17 09:40</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/17/094055" data-hatena-star-title="Google Ad Manager(GAM)でサードパーティクリエイティブのクリック数が取得できない場合の対処法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/17/094055" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/17/094055" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/17/094055"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?text=Google+Ad+Manager%EF%BC%88GAM%EF%BC%89%E3%81%A7%E3%82%B5%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3%E3%82%AF%E3%83%AA%E3%82%A8%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E3%81%AE%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E6%95%B0%E3%81%8C%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AE%E5%AF%BE%E5%87%A6%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F17%2F094055" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-2000 words-200 mode-markdown entry-even" id="entry-6802418398335332414" data-keyword-campaign="" data-uuid="6802418398335332414" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/11" rel="nofollow"> <time datetime="2025-03-11T00:55:40Z" title="2025-03-11T00:55:40Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">11</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/11/095540" class="entry-title-link bookmark">【iOS/Android】環境別にAdMobのネイティブ広告バリデーターを非表示にする方法</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p>こんにちは。エキサイトでアプリエンジニアをしている岡島です。 今回は、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> AdMobのネイティブ広告のバリデーターについて、環境別に表示非表示を切り替える方法を共有したいと思います。</p> <p>本記事では環境設定に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a> は productFlavors を使用し、<a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a> は xcconfig を用いる場合 の設定方法を紹介していきます。</p> <ul class="table-of-contents"> <li><a href="#Google-AdMobのネイティブ広告バリデーターについて">Google AdMobのネイティブ広告バリデーターについて</a></li> <li><a href="#Androidで表示非表示を切り替える">Androidで表示・非表示を切り替える</a><ul> <li><a href="#appbuildgradle-に設定を追加">app/build.gradle に設定を追加</a></li> <li><a href="#AndroidManifestxmlに設定を追加">AndroidManifest.xmlに設定を追加</a></li> </ul> </li> <li><a href="#iOSで表示非表示を切り替える">iOSで表示・非表示を切り替える</a><ul> <li><a href="#xcconfig-ファイルに環境ごとの設定を追加">xcconfig ファイルに環境ごとの設定を追加</a></li> <li><a href="#Infoplist-に設定を追加">Info.plist に設定を追加</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> <li><a href="#参考文献">参考文献</a></li> </ul> <h1 id="Google-AdMobのネイティブ広告バリデーターについて"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> AdMobのネイティブ広告バリデーターについて</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Google">Google</a> AdMobのネイティブ広告バリデーターは、開発中に広告のレイアウトや表示形式が適切かを確認するためのツールです。</p> <p>デフォルトでこのバリデーターは表示されるようになっているため、開発の際に、dev環境やstage環境などで表示されていました。開発時以外の動作確認など、このポップアップが不要な場合があります。そこで環境別にこのバリデーター表示の表示・非表示を切り替えたいと思ったので、環境別に設定する方法について調べてみました。</p> <p><figure class="figure-image figure-image-fotolife" title="AdMob validator"> <span itemscope itemtype="http://schema.org/Photograph"><a href="http://f.hatena.ne.jp/excite-okazi/20250310155211" class="hatena-fotolife" itemprop="url"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excite-okazi/20250310/20250310155211.png" width="890" height="344" loading="lazy" title="" class="hatena-fotolife" style="width:250px" itemprop="image"></a></span> <figcaption>AdMob validator</figcaption> </figure></p> <h1 id="Androidで表示非表示を切り替える"><a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a>で表示・非表示を切り替える</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Android">Android</a> では、app/build.gradle で manifestPlaceholders を環境ごとに設定し、それを AndroidManifest.<a class="keyword" href="https://d.hatena.ne.jp/keyword/xml">xml</a> で適用することでバリデーターの表示を切り替えることができます。</p> <h3 id="appbuildgradle-に設定を追加">app/build.gradle に設定を追加</h3> <pre class="code lang-groovy" data-lang="groovy" data-unlink>android { flavorDimensions <span class="synConstant">"environment"</span> productFlavors { development { dimension <span class="synConstant">"environment"</span> manifestPlaceholders = [ <span class="synStatement"> NATIVE_AD_DEBUGGER_ENABLED</span>: <span class="synConstant">"true"</span> ] } staging { dimension <span class="synConstant">"environment"</span> manifestPlaceholders = [ <span class="synStatement"> NATIVE_AD_DEBUGGER_ENABLED</span>: <span class="synConstant">"false"</span> ] } } } </pre> <h3 id="AndroidManifestxmlに設定を追加">AndroidManifest.<a class="keyword" href="https://d.hatena.ne.jp/keyword/xml">xml</a>に設定を追加</h3> <p>次に、AndroidManifest.<a class="keyword" href="https://d.hatena.ne.jp/keyword/xml">xml</a>で manifestPlaceholders の値を適用します。</p> <pre class="code lang-xml" data-lang="xml" data-unlink><span class="synIdentifier"><manifest></span> <span class="synIdentifier"><application></span> <span class="synIdentifier"><meta-data</span> <span class="synIdentifier"> </span><span class="synType">android</span><span class="synComment">:</span><span class="synType">name</span>=<span class="synConstant">"com.google.android.gms.ads.flag.NATIVE_AD_DEBUGGER_ENABLED"</span> <span class="synIdentifier"> </span><span class="synType">android</span><span class="synComment">:</span><span class="synType">value</span>=<span class="synConstant">"${NATIVE_AD_DEBUGGER_ENABLED}"</span><span class="synIdentifier"> /></span> <span class="synIdentifier"></application></span> <span class="synIdentifier"></manifest></span> </pre> <p>こうすることで、development環境ではバリデーターを表示し、staging環境では非表示にすることができます。</p> <h1 id="iOSで表示非表示を切り替える"><a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>で表示・非表示を切り替える</h1> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/iOS">iOS</a>では、xcconfigファイルを使用して<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%C4%B6%AD%CA%D1%BF%F4">環境変数</a>を設定し、それをInfo.plistで使用します。</p> <h3 id="xcconfig-ファイルに環境ごとの設定を追加">xcconfig ファイルに環境ごとの設定を追加</h3> <p>例<br/> <a class="keyword" href="https://d.hatena.ne.jp/keyword/ios">ios</a>/config/development.xcconfig</p> <pre class="code lang-config" data-lang="config" data-unlink><span class="synIdentifier">GAD_NATIVE_AD_VALIDATOR_ENABLED</span><span class="synStatement">=</span>false </pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ios">ios</a>/config/staging.xcconfig</p> <pre class="code lang-config" data-lang="config" data-unlink><span class="synIdentifier">GAD_NATIVE_AD_VALIDATOR_ENABLED</span><span class="synStatement">=</span>true </pre> <h3 id="Infoplist-に設定を追加">Info.plist に設定を追加</h3> <p>Info.plist に以下の設定を追加します。</p> <pre class="code lang-xml" data-lang="xml" data-unlink><span class="synIdentifier"><key></span>GADNativeAdValidatorEnabled<span class="synIdentifier"></key></span> <span class="synIdentifier"><string></span>$(GAD_NATIVE_AD_VALIDATOR_ENABLED)<span class="synIdentifier"></string></span> </pre> <h1 id="まとめ">まとめ</h1> <p>今回は、開発環境ごとにバリデーターのポップアップを切り替える方法を共有しました。manifestPlaceholders や .xcconfig を活用することで、簡単に設定を切り替えることができました。</p> <h1 id="参考文献">参考文献</h1> <p><a href="https://developers.google.com/admob/ios/native/validator?hl=ja">https://developers.google.com/admob/ios/native/validator?hl=ja</a></p> <p><a href="https://developers.google.com/admob/android/native/validator?hl=ja">https://developers.google.com/admob/android/native/validator?hl=ja</a></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/admob" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">admob</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="excite-okazi" >excite-okazi</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/11/095540"><time data-relative datetime="2025-03-11T00:55:40Z" title="2025-03-11T00:55:40Z" class="updated">2025-03-11 09:55</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/11/095540" data-hatena-star-title="【iOS/Android】環境別にAdMobのネイティブ広告バリデーターを非表示にする方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/11/095540" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/11/095540" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/11/095540"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=admob&text=%E3%80%90iOS%2FAndroid%E3%80%91%E7%92%B0%E5%A2%83%E5%88%A5%E3%81%ABAdMob%E3%81%AE%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96%E5%BA%83%E5%91%8A%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%BF%E3%83%BC%E3%82%92%E9%9D%9E%E8%A1%A8%E7%A4%BA%E3%81%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F11%2F095540" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-1600 words-100 mode-markdown entry-odd" id="entry-6802418398335239526" data-keyword-campaign="" data-uuid="6802418398335239526" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/10" rel="nofollow"> <time datetime="2025-03-09T23:56:02Z" title="2025-03-09T23:56:02Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">10</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/10/085602" class="entry-title-link bookmark">Tailwind CSSでHTML属性に対する疑似クラスのスタイリングをする方法</a> </h1> <div class="entry-categories categories"> <a href="https://tech.excite.co.jp/archive/category/Tailwind%20CSS" class="entry-category-link category-Tailwind-CSS">Tailwind CSS</a> <a href="https://tech.excite.co.jp/archive/category/CSS" class="entry-category-link category-CSS">CSS</a> <a href="https://tech.excite.co.jp/archive/category/%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0" class="entry-category-link category-スタイリング">スタイリング</a> <a href="https://tech.excite.co.jp/archive/category/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89" class="entry-category-link category-フロントエンド">フロントエンド</a> </div> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/saitoayumu/20250310/20250310084824.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。エキサイトでデザイナーをしている齋藤です。</p> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>でHTML属性に対する疑似要素のスタイリングをする方法をご紹介します。</p> <ul class="table-of-contents"> <li><a href="#実現したいこと">実現したいこと</a></li> <li><a href="#Tailwind-CSSで擬似クラスを指定する方法">Tailwind CSSで擬似クラスを指定する方法</a></li> <li><a href="#注意点">注意点</a><ul> <li><a href="#CSS">CSS</a></li> <li><a href="#Tailwind-CSS">Tailwind CSS</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a><ul> <li><a href="#参考文献">参考文献</a></li> </ul> </li> </ul> <h2 id="実現したいこと">実現したいこと</h2> <p>冒頭、実現したいことを整理します。</p> <p>HTMLの各タグには属性が用意されていることがあり、要素の動作を制御することができます。</p> <p><code>input</code>タグの<code>disabled</code>属性などがこれにあたります。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>ではHTML属性に対して疑似クラスが存在し、<code>{タグ}:{属性名}</code>の様式でスタイリングすることができます。</p> <pre class="code lang-css" data-lang="css" data-unlink><span class="synStatement">input</span>:<span class="synPreProc">disabled</span> <span class="synIdentifier">{</span> <span class="synType">background</span>: <span class="synConstant">red</span>; <span class="synIdentifier">}</span> </pre> <p>お示しした<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>の例では、「<code>disabled</code>属性が<code>true</code>の<code>input</code>要素は背景が赤になる」というスタイリングを表現しています。</p> <p>一方で今回実現したいのは、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>でTailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>でHTML属性に対する疑似クラスのスタイリングをすることです。</p> <h2 id="Tailwind-CSSで擬似クラスを指定する方法">Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>で擬似クラスを指定する方法</h2> <p>結論から申し上げると、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>では <code>{属性名}:{ユーティリティクラス}</code> の様式を用いることで、擬似クラスを用いたスタイリングが可能です。</p> <p>先の例をTailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>で表現すると以下のようになります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">input</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"disabled:bg-red-500"</span><span class="synIdentifier"> ...></span> </pre> <p><code>disabled</code>属性以外にも用意されています。対応一覧は<a href="https://tailwindcss.com/docs/hover-focus-and-other-states#pseudo-class-reference">公式ドキュメント</a>をご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2Fdocs%2Fhover-focus-and-other-states%23pseudo-class-reference" title="Hover, focus, and other states - Core concepts" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/docs/hover-focus-and-other-states#pseudo-class-reference">tailwindcss.com</a></cite></p> <h2 id="注意点">注意点</h2> <p>擬似クラスに対して複数のユーティリティクラスを指定したい場合は、都度接頭辞として<code>{属性名}:</code>を付与する必要があります。</p> <h3 id="CSS"><a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a></h3> <p>純粋な<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>では宣言ブロック内に複数のプロパティを記述することが可能です。</p> <pre class="code lang-css" data-lang="css" data-unlink><span class="synStatement">input</span>:<span class="synPreProc">disabled</span> <span class="synIdentifier">{</span> <span class="synType">background</span>: <span class="synConstant">red</span>; <span class="synType">border-color</span>: <span class="synConstant">red</span>; <span class="synIdentifier">}</span> </pre> <h3 id="Tailwind-CSS">Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a></h3> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>の場合は、ユーティリティクラスに対して都度接頭辞として<code>{属性名}:</code>を付与します。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier"><</span><span class="synStatement">input</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">"disabled:bg-red-500 disabled:border-red-500"</span><span class="synIdentifier"> ...></span> </pre> <h2 id="まとめ">まとめ</h2> <p>今回は、Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>でHTML属性に対する疑似要素のスタイリングをする方法をご紹介しました。</p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>は純粋な<a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>の機能と互換するように設計されているため、書き味は異なりますがスタイリングの幅はほぼ変わらずに十分な表現ができます。</p> <p>一方で、擬似クラスなどを多用していくとどうしても可読性は低下します。</p> <p>可読性を担保するためにPrettierの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>を使用して、<code>class</code>属性内を規則的に並び替えるようにすることをおすすめします。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2024%2F06%2F10%2F145429" title="【JetBrains製エディタ対応】PrettierでTailwind CSSのclass名を規則的に自動ソーティングさせる - エキサイト TechBlog." class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.excite.co.jp/entry/2024/06/10/145429">tech.excite.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2024%2F06%2F24%2F125911" title="pre-commitを使用してgit commit時にPrettierを実行させる - エキサイト TechBlog." class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.excite.co.jp/entry/2024/06/24/125911">tech.excite.co.jp</a></cite></p> <p>Tailwind <a class="keyword" href="https://d.hatena.ne.jp/keyword/CSS">CSS</a>を使用される方の一助となれば幸いです。ご精読ありがとうございました。</p> <h3 id="参考文献">参考文献</h3> <ul> <li><a href="https://tailwindcss.com/docs/hover-focus-and-other-states#pseudo-classes">Hover, focus, and other states - Core concepts - Tailwind CSS</a></li> </ul> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="saitoayumu" >saitoayumu</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/10/085602"><time data-relative datetime="2025-03-09T23:56:02Z" title="2025-03-09T23:56:02Z" class="updated">2025-03-10 08:56</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/10/085602" data-hatena-star-title="Tailwind CSSでHTML属性に対する疑似クラスのスタイリングをする方法" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/10/085602" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/10/085602" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/10/085602"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?text=Tailwind+CSS%E3%81%A7HTML%E5%B1%9E%E6%80%A7%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E7%96%91%E4%BC%BC%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F10%2F085602" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-1200 words-100 mode-markdown entry-even" id="entry-6802418398334411883" data-keyword-campaign="" data-uuid="6802418398334411883" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/07" rel="nofollow"> <time datetime="2025-03-07T01:50:26Z" title="2025-03-07T01:50:26Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">07</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/07/105026" class="entry-title-link bookmark">アコーディオンで使用するアイコンはどれがいい?</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/designsazuka/20250307/20250307101016.png" width="1200" height="677" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!<a href="https://counselor.excite.co.jp/">エキサイトお悩み相談室</a>でデザイナーをしているサヅカです。</p> <p>サイトをもっと使いやすくするべく日々UI改修を行っているのですが、今回アイコンの選定に非常に悩みましたのでその時のお話をしようと思います。</p> <h2 id="アイコンの意味を他のデザイナーと考えてみた">アイコンの意味を他のデザイナーと考えてみた</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B3%A1%BC%A5%C7%A5%A3%A5%AA%A5%F3">アコーディオン</a>=クリックするとビヨーンとさらに内容が表示されるアレなのですが、ページ改修をしていて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B3%A1%BC%A5%C7%A5%A3%A5%AA%A5%F3">アコーディオン</a>のデザイン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AC%A5%A4%A5%C9%A5%E9%A5%A4%A5%F3">ガイドライン</a>はまだ設定していないことに気付きました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/designsazuka/20250307/20250307103545.png" width="1200" height="684" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こちらは「エキサイトお悩み相談室」のコイン購入ページの一部になります。</p> <p>今回<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B3%A1%BC%A5%C7%A5%A3%A5%AA%A5%F3">アコーディオン</a>化することになったので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>下、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>右、プラスマイナスの3種類を提案しました。</p> <h4 id="シェブロン下開くとシェブロンは上になる"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>下(開くと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>は上になる)</h4> <p>👩🦰:一番よく見かける気がする</p> <p>👨:<a href="https://www.w3.org/">Web標準化団体w3c</a>でも用いられているようです</p> <h4 id="シェブロン右開くとシェブロンは下になる"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>右(開くと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>は下になる)</h4> <p>👩🦰:他のページに遷移しそう</p> <p>👨:右と下というのが動きに統一感がない</p> <h4 id="プラスマイナス開くとプラスはマイナスになる">プラスマイナス(開くとプラスはマイナスになる)</h4> <p>👩🦰:「コイン購入」ページということもあり「+」は混乱しそう</p> <p>👨:コンテンツの内容が多い時に使うイメージ</p> <p>👱♀️:プラスマイナスアイコンは追加/削除の意味を持っていて<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%B3%A1%BC%A5%C7%A5%A3%A5%AA%A5%F3">アコーディオン</a>以外でも頻用されると思う</p> <p>色々な人と意見交換して検討した結果、弊サービスでは「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>下」を採用することにしました。</p> <p>個人的にはプラスマイナスが分かりやすくて好きなのですが、デザイナーの好みで安易に決めてしまうとユーザーが混乱するので気を付けたいと思います。</p> <h2 id="おまけくみたいなやつってもう言わない">おまけ「”く”みたいなやつ」ってもう言わない</h2> <p>上の記事で<strong>「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>」</strong>という言葉を使いました。矢印みたいな、ひらがなの”く”みたいなやつのことです。</p> <p>正式名称を知りたくて色々と調べました。</p> <h4 id="シェブロン"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a></h4> <p>服の生地などで「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>柄」というのがあるのですが、どうやらそこからきているようです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/designsazuka/20250307/20250307102937.png" width="960" height="512" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="キャレット">キャレット</h4> <p>キーボードの「キャレット」という記号のことで、「キャレット右」というデザイナーも多いようです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/designsazuka/20250307/20250307102951.png" width="960" height="520" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="結局のところ正式名称は">結局のところ正式名称は?</h2> <p>特にこれといって正式なものはないようですが、<a href="https://fonts.google.com/icons?icon.query=chevron">マテリアルアイコン</a>では”Chevron Right”という名前でしたので「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%D6%A5%ED%A5%F3">シェブロン</a>」と呼ぶのが良いかもしれません。浸透するまではチーム内では「矢印」等で会話しそうですが汗</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/d/designsazuka/20250307/20250307103006.png" width="960" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">webデザイン</span> </a> </span> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">アイコン</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="designsazuka" >designsazuka</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/07/105026"><time data-relative datetime="2025-03-07T01:50:26Z" title="2025-03-07T01:50:26Z" class="updated">2025-03-07 10:50</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_medium=button&utm_campaign=subscribe_blog&utm_source=blogs_entry_footer"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/07/105026" data-hatena-star-title="アコーディオンで使用するアイコンはどれがいい?" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/07/105026" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/07/105026" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/07/105026"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3&hashtags=%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3&text=%E3%82%A2%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AA%E3%83%B3%E3%81%A7%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%81%AF%E3%81%A9%E3%82%8C%E3%81%8C%E3%81%84%E3%81%84%EF%BC%9F+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F07%2F105026" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <article class="entry hentry test-hentry js-entry-article date-first autopagerize_page_element chars-4000 words-400 mode-markdown entry-odd" id="entry-6801883189111452890" data-keyword-campaign="" data-uuid="6801883189111452890" data-publication-type="entry"> <div class="entry-inner"> <header class="entry-header"> <div class="date entry-date first"> <a href="https://tech.excite.co.jp/archive/2025/03/04" rel="nofollow"> <time datetime="2025-03-04T07:08:19Z" title="2025-03-04T07:08:19Z"> <span class="date-year">2025</span><span class="hyphen">-</span><span class="date-month">03</span><span class="hyphen">-</span><span class="date-day">04</span> </time> </a> </div> <h1 class="entry-title"> <a href="https://tech.excite.co.jp/entry/2025/03/04/160819" class="entry-title-link bookmark"> [Java]ローカルキャッシュを導入した話</a> </h1> </header> <div class="entry-content hatenablog-entry"> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240624/20240624124126.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="はじめに">はじめに</h2> <p>こんにちは、メディアプラットフォーム事業部エンジニアの岡崎です。最近、ローカルキャッシュの導入を行いました。今回はその時のことを備忘録としてブログに残します。</p> <h2 id="前提">前提</h2> <p>今回の要件として一番大切なことは、できるだけユーザーがページを見ることができる状態にすることでした。</p> <p>これを前提として、今までの実装を見ていきましょう。</p> <p>最初、キャッシュはRedisに保存するような構成になっていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240603/20240603134837.png" width="1192" height="770" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この場合、もしDBに障害が起きたとしても、Redisに保存しているデータがあった場合は、そこから取得することができます。</p> <p>しかし、Redisに障害が起きてしまったら、データが取得できなくなり、クライアントではページを表示できなくなってしまいます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240603/20240603134906.png" width="1192" height="794" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今回は要件で、できるだけユーザーがページを見れる状態にしたかったので、他にもっといい方法がないか模索することになりました。</p> <p>そこで、キャッシュしたデータを保存する場所をRedisからローカルに変更しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/ooo-ka999/20240603/20240603134921.png" width="1200" height="809" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こうすることで、DBに障害が起きたとしても、ローカルサーバーにデータがある限りは、データを取得できます。</p> <h2 id="環境">環境</h2> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Java">Java</a></li> </ul> <pre class="code lang-java" data-lang="java" data-unlink>openjdk version <span class="synConstant">"21.0.2"</span> <span class="synConstant">2024</span>-<span class="synConstant">01</span>-<span class="synConstant">16</span> LTS OpenJDK Runtime Environment Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS) OpenJDK <span class="synConstant">64</span>-Bit Server VM Corretto-<span class="synConstant">21.0.2.13.1</span> (build <span class="synConstant">21.0.2</span>+<span class="synConstant">13</span>-LTS, mixed mode, sharing) </pre> <ul> <li>Spring Boot</li> </ul> <pre class="code lang-java" data-lang="java" data-unlink> . ____ _ __ _ _ /<span class="synError">\\</span> / ___'_ __ _ _(_)_ __ __ _ <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> ( ( )<span class="synError">\</span>___ | <span class="synConstant">'</span><span class="synError">_ | </span><span class="synConstant">'</span>_| | '_ <span class="synError">\</span>/ _<span class="synError">`</span> | <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\</span> <span class="synError">\\</span>/ ___)| |_<span class="synError">)</span>| | | | | || (_| | ) <span class="synError">)</span> <span class="synError">)</span> <span class="synError">)</span> ' |____| .__|_| |_|_| |_<span class="synError">\</span>__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3<span class="synConstant">.2.2</span>) </pre> <ul> <li>build.gradleに以下の依存関係を追加する</li> </ul> <pre class="code lang-yaml" data-lang="yaml" data-unlink>implementation <span class="synConstant">"com.github.ben-manes.caffeine:caffeine:3.1.8"</span> </pre> <h2 id="実装">実装</h2> <p>まずは、ローカルキャッシュを使うための準備を行いました。</p> <p>必要なものは以下の通りです。</p> <ul> <li>ローカル用のキャッシュマネージャー</li> <li>Redis用のキャッシュマネージャー</li> <li>ローカルのキャッシュキー</li> </ul> <p>これらを、それぞれのファイルに実装していきました。</p> <p>その時の実装例を以下に示します。</p> <h3 id="CacheConfig">CacheConfig</h3> <p>ここでは、ローカルキャッシュのキャッシュマネージャーの設定を行っています。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Configuration</span> <span class="synType">public</span> <span class="synType">class</span> CacheConfig { <span class="synComment">/**</span> <span class="synComment"> *</span><span class="synSpecial"> ローカルキャッシュ用のキャッシュマネージャーを設定する</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synSpecial">@return</span><span class="synComment"> キャッシュマネージャー</span> <span class="synComment"> */</span> <span class="synPreProc">@Bean</span>(CacheLocalKeyType.LOCAL_CACHE_MANAGER_NAME) <span class="synType">public</span> CacheManager localCacheManager() { CaffeineCacheManager cacheManager = <span class="synStatement">new</span> CaffeineCacheManager(); Arrays.stream(CacheLocalKeyType.values()).forEach(e -> { Cache<Object, Object> cache = Caffeine .newBuilder() .expireAfterWrite(Duration.ofSeconds(e.getTtl())) .build(); cacheManager.registerCustomCache(e.getKey(), cache); }); <span class="synStatement">return</span> cacheManager; } } </pre> <h3 id="CacheLocalKeyType">CacheLocalKeyType</h3> <p>ローカルのキャッシュキーの実装例です。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synType">public</span> <span class="synType">enum</span> CacheLocalKeyType { WEB_GET_TOP(CacheLocalKeyType.WEB_TOP_KEY, TimeUnit.HOURS.toSeconds(<span class="synConstant">1</span>)); <span class="synPreProc">@Getter</span> <span class="synType">private</span> <span class="synType">final</span> String key; <span class="synPreProc">@Getter</span> <span class="synType">private</span> <span class="synType">final</span> Long ttl; <span class="synComment">/**</span> <span class="synComment"> *</span><span class="synSpecial"> ローカルキャッシュのキータイプ</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synSpecial">@param</span><span class="synIdentifier"> cacheName</span><span class="synComment"> キャッシュ名</span> <span class="synComment"> * </span><span class="synSpecial">@param</span><span class="synIdentifier"> ttl</span><span class="synComment"> キャッシュ時間</span> <span class="synComment"> */</span> CacheLocalKeyType(String cacheName, Long ttl) { <span class="synType">this</span>.key = cacheName; <span class="synType">this</span>.ttl = ttl; } <span class="synType">public</span> <span class="synType">static</span> <span class="synType">final</span> String SAMPLE_KEY = <span class="synConstant">"sampleKey"</span>; <span class="synType">public</span> <span class="synType">static</span> <span class="synType">final</span> String LOCAL_CACHE_MANAGER_NAME = <span class="synConstant">"localCacheManager"</span>; } </pre> <h3 id="Redisconfig">Redis.config</h3> <p>Redis.configにキャッシュマネージャーがない場合、実装する必要があります。</p> <p>今回は、Redisのキャッシュをデフォルトの設定にしたいため、<code>@Primary</code>をつけます。</p> <pre class="code lang-java" data-lang="java" data-unlink> <span class="synComment">/**</span> <span class="synComment"> *</span><span class="synSpecial"> Redisのキャッシュマネージャーを設定する</span> <span class="synComment"> *</span> <span class="synComment"> * </span><span class="synSpecial">@param</span><span class="synIdentifier"> redisConnectionFactory</span><span class="synComment"> redisConnectionFactory</span> <span class="synComment"> * </span><span class="synSpecial">@return</span><span class="synComment"> キャッシュマネージャー</span> <span class="synComment"> */</span> <span class="synPreProc">@Bean</span> <span class="synPreProc">@Primary</span> <span class="synType">public</span> CacheManager cacheManager(LettuceConnectionFactory redisConnectionFactory) { Map<String, RedisCacheConfiguration> cacheConfigurations = <span class="synStatement">new</span> HashMap<>(); Arrays.stream(CacheKeyType.values()).forEach(e -> cacheConfigurations.put( e.getKey(), RedisCacheConfiguration .defaultCacheConfig() .entryTtl(Duration.ofSeconds(e.getTtl())) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(<span class="synType">this</span>.serializer())) ) ); <span class="synStatement">return</span> RedisCacheManager .builder(redisConnectionFactory) .withInitialCacheConfigurations(cacheConfigurations) .build(); } </pre> <p>以上で、ローカルキャッシュを使う準備は整いました。</p> <p>それでは、実際にローカルキャッシュを使ってみましょう。実装例は以下の通りです。</p> <pre class="code lang-java" data-lang="java" data-unlink><span class="synPreProc">@Override</span> <span class="synComment">// キャッシュマネージャーを指定しない場合、デフォルトの設定が使われます</span> <span class="synPreProc">@Cacheable</span>(cacheNames = CacheKeyType.SAMPLE_KEY, cacheManager = CacheLocalKeyType.LOCAL_CACHE_MANAGER_NAME) <span class="synType">public</span> String getIdList() { <span class="synComment">// 以下略</span> } </pre> <p>これで完成です!</p> <h2 id="最後に">最後に</h2> <p>今回は、ローカルキャッシュを導入したので、簡単に紹介しました。簡単に実装ができるので、ぜひローカルキャッシュの実装の導入を検討してみてください。</p> <p>最後に、採用情報のお知らせです。エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>も歓迎していますので、興味があれば連絡いただければと思います。</p> <p>募集職種一覧はこちらになります!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2Fexcite%2Fprojects" title="エキサイトホールディングス株式会社の募集・採用・求人情報 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/excite/projects">www.wantedly.com</a></cite></p> </div> <footer class="entry-footer"> <div class="entry-tags-wrapper"> <div class="entry-tags"> <span class="entry-tag"> <a href="https://d.hatena.ne.jp/keyword/Java" class="entry-tag-link"> <span class="entry-tag-icon">#</span><span class="entry-tag-label">Java</span> </a> </span> </div> </div> <p class="entry-footer-section track-inview-by-gtm" data-gtm-track-json="{"area": "finish_reading"}"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="ooo-ka999" >ooo-ka999</span></span> <span class="entry-footer-time"><a href="https://tech.excite.co.jp/entry/2025/03/04/160819"><time data-relative datetime="2025-03-04T07:08:19Z" title="2025-03-04T07:08:19Z" class="updated">2025-03-04 16:08</time></a></span> <span class=" entry-footer-subscribe " data-test-blog-controlls-subscribe> <a href="https://blog.hatena.ne.jp/excitech/excitech.hatenablog.com/subscribe?utm_campaign=subscribe_blog&utm_source=blogs_entry_footer&utm_medium=button"> 読者になる </a> </span> </p> <div class="hatena-star-container" data-hatena-star-container data-hatena-star-url="https://tech.excite.co.jp/entry/2025/03/04/160819" data-hatena-star-title=" [Java]ローカルキャッシュを導入した話" data-hatena-star-variant="profile-icon" data-hatena-star-profile-url-template="https://blog.hatena.ne.jp/{username}/" ></div> <div class="social-buttons"> <div class="social-button-item"> <a href="https://b.hatena.ne.jp/entry/s/tech.excite.co.jp/entry/2025/03/04/160819" class="hatena-bookmark-button" data-hatena-bookmark-url="https://tech.excite.co.jp/entry/2025/03/04/160819" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="https://tech.excite.co.jp/entry/2025/03/04/160819"></div> </div> <div class="social-button-item"> <a class="entry-share-button entry-share-button-twitter test-share-button-twitter" href="https://x.com/intent/tweet?hashtags=Java&text=+%5BJava%5D%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%9F%E8%A9%B1+-+%E3%82%A8%E3%82%AD%E3%82%B5%E3%82%A4%E3%83%88+TechBlog.&url=https%3A%2F%2Ftech.excite.co.jp%2Fentry%2F2025%2F03%2F04%2F160819" title="X(Twitter)で投稿する" ></a> </div> </div> <div class="customized-footer"> </div> <div class="comment-box js-comment-box"> <ul class="comment js-comment"> <li class="read-more-comments" style="display: none;"><a>もっと読む</a></li> </ul> <a class="leave-comment-title js-leave-comment-title">コメントを書く</a> </div> </footer> </div> </article> <!-- rakuten_ad_target_end --> <!-- google_ad_section_end --> <div class="pager autopagerize_insert_before"> <span class="pager-next"> <a href="https://tech.excite.co.jp/?page=1741072099" rel="next">次のページ</a> </span> </div> </div> </div> <aside id="box1"> <div id="box1-inner"> </div> </aside> </div><!-- #wrapper --> <aside id="box2"> <div id="box2-inner"> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> <a href="https://www.wantedly.com/companies/excite"> <img style="border: solid 1px #D91B31; border-radius: 4px;" src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/excitech/20210507/20210507182307.png" alt="20210428112826" width="300px"></a> </div> </div> <div class="hatena-module hatena-module-category"> <div class="hatena-module-title"> カテゴリー </div> <div class="hatena-module-body"> <ul class="hatena-urllist"> <li> <a href="https://tech.excite.co.jp/archive/category/Beer%20Bash" class="category-Beer-Bash"> Beer Bash (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Tailwind%20CSS" class="category-Tailwind-CSS"> Tailwind CSS (20) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0" class="category-スタイリング"> スタイリング (24) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89" class="category-フロントエンド"> フロントエンド (46) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/CSS" class="category-CSS"> CSS (11) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Flutter" class="category-Flutter"> Flutter (43) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Google%20Ad%20Manager" class="category-Google-Ad-Manager"> Google Ad Manager (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A2%E3%83%89%E3%83%86%E3%82%AF%E3%83%8E%E3%83%AD%E3%82%B8%E3%83%BC" class="category-アドテクノロジー"> アドテクノロジー (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/UX%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3" class="category-UXデザイン"> UXデザイン (6) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/HTMX" class="category-HTMX"> HTMX (5) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/UI%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3" class="category-UIデザイン"> UIデザイン (21) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97" class="category-マークアップ"> マークアップ (16) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Design" class="category-Design"> Design (45) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/SaaS%E4%BA%8B%E6%A5%AD%E9%83%A8" class="category-SaaS事業部"> SaaS事業部 (30) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Figma" class="category-Figma"> Figma (19) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/LottieFiles" class="category-LottieFiles"> LottieFiles (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3" class="category-アニメーション"> アニメーション (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Dart" class="category-Dart"> Dart (7) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B7%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3" class="category-アクセシビリティ"> アクセシビリティ (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/OpenAPI" class="category-OpenAPI"> OpenAPI (6) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Canva" class="category-Canva"> Canva (12) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Alpine.js" class="category-Alpine.js"> Alpine.js (6) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/HTML" class="category-HTML"> HTML (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/AI" class="category-AI"> AI (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Python" class="category-Python"> Python (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0" class="category-デザインシステム"> デザインシステム (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Android" class="category-Android"> Android (9) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%83%E3%83%97" class="category-インターンシップ"> インターンシップ (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9" class="category-インフラ"> インフラ (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/vue" class="category-vue"> vue (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Laravel" class="category-Laravel"> Laravel (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89" class="category-バックエンド"> バックエンド (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/PHP" class="category-PHP"> PHP (4) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Thymeleaf" class="category-Thymeleaf"> Thymeleaf (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E6%96%B0%E5%8D%92%E7%A0%94%E4%BF%AE" class="category-新卒研修"> 新卒研修 (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Git" class="category-Git"> Git (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/AWS" class="category-AWS"> AWS (14) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Spring%20Boot" class="category-Spring-Boot"> Spring Boot (4) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Terraform" class="category-Terraform"> Terraform (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E6%8A%80%E8%A1%93%E7%B5%84%E7%B9%94" class="category-技術組織"> 技術組織 (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/IntelliJ" class="category-IntelliJ"> IntelliJ (2) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Java" class="category-Java"> Java (6) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Excite%20%C3%97%20iXIT%20TechCon%202024" class="category-Excite-×-iXIT-TechCon-2024"> Excite × iXIT TechCon 2024 (6) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/TechCon" class="category-TechCon"> TechCon (5) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/ShellScript" class="category-ShellScript"> ShellScript (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Docker" class="category-Docker"> Docker (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/React" class="category-React"> React (4) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Go" class="category-Go"> Go (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Nuxt.js" class="category-Nuxt.js"> Nuxt.js (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Next.js" class="category-Next.js"> Next.js (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Azure" class="category-Azure"> Azure (5) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Redis" class="category-Redis"> Redis (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/AWS%20CDK" class="category-AWS-CDK"> AWS CDK (15) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Excite%20%C3%97%20iXIT%20TechCon%202023" class="category-Excite-×-iXIT-TechCon-2023"> Excite × iXIT TechCon 2023 (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/PHP8" class="category-PHP8"> PHP8 (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Oracle" class="category-Oracle"> Oracle (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Database" class="category-Database"> Database (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/GitHub" class="category-GitHub"> GitHub (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/Firebase" class="category-Firebase"> Firebase (1) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/MySQL" class="category-MySQL"> MySQL (4) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/%E5%A4%B1%E6%95%97%E8%AB%87" class="category-失敗談"> 失敗談 (3) </a> </li> <li> <a href="https://tech.excite.co.jp/archive/category/GoogleAnalytics4" class="category-GoogleAnalytics4"> GoogleAnalytics4 (1) </a> </li> </ul> </div> </div> <div class="hatena-module hatena-module-recent-entries "> <div class="hatena-module-title"> <a href="https://tech.excite.co.jp/archive"> 最新記事 </a> </div> <div class="hatena-module-body"> <ul class="recent-entries hatena-urllist "> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://tech.excite.co.jp/entry/2025/04/01/123003" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">高速なテンプレートエンジンJTEをSpringBootで試す</a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://tech.excite.co.jp/entry/2025/03/31/183521" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title"> [htmx] hx-triggerを使ってinfinitScrollを実装する方法[Java/Spring Boot]</a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://tech.excite.co.jp/entry/2025/03/31/170000" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">第11回テクデザBeer Bashを開催しました</a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://tech.excite.co.jp/entry/2025/03/31/162407" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">Tailwind CSSでbackground-sizeとbackground-positionの両方を任意の値で設定する方法</a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="https://tech.excite.co.jp/entry/2025/03/31/152105" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title">【Flutter】flutter_flavorizrを用いて環境分けをする</a> </div> </li> </ul> </div> </div> <div class="hatena-module hatena-module-profile"> <div class="hatena-module-title"> プロフィール </div> <div class="hatena-module-body"> <a href="https://tech.excite.co.jp/about" class="profile-icon-link"> <img src="https://cdn.profile-image.st-hatena.com/users/excitech/profile.png?1620728663" alt="id:excitech" class="profile-icon" /> </a> <div class="profile-description"> <p>エキサイトテックブログを運営しています。月10投稿を目指していますので、気軽にのぞいてくださると嬉しいです!</p> </div> <div class="hatena-follow-button-box btn-subscribe js-hatena-follow-button-box" > <a href="#" class="hatena-follow-button js-hatena-follow-button"> <span class="subscribing"> <span class="foreground">読者です</span> <span class="background">読者をやめる</span> </span> <span class="unsubscribing" data-track-name="profile-widget-subscribe-button" data-track-once> <span class="foreground">読者になる</span> <span class="background">読者になる</span> </span> </a> <div class="subscription-count-box js-subscription-count-box"> <i></i> <u></u> <span class="subscription-count js-subscription-count"> </span> </div> </div> <div class="profile-about"> <a href="https://tech.excite.co.jp/about">このブログについて</a> </div> </div> </div> <div class="hatena-module hatena-module-search-box"> <div class="hatena-module-title"> 検索 </div> <div class="hatena-module-body"> <form class="search-form" role="search" action="https://tech.excite.co.jp/search" method="get"> <input type="text" name="q" class="search-module-input" value="" placeholder="記事を検索" required> <input type="submit" value="検索" class="search-module-button" /> </form> </div> </div> <div class="hatena-module hatena-module-profile"> <div class="hatena-module-title"> プロフィール </div> <div class="hatena-module-body"> <div class="hatena-follow-button-box"> <a href="https://twitter.com/excite_tech" title="X(Twitter)アカウント" class="btn-twitter" data-lang="ja"> <img src="https://cdn.blog.st-hatena.com/images/theme/plofile-socialize-x.svg?version=15b44d6f3386d0946efd031c34207d" alt="X"> <span> @excite_techをフォロー </span> </a> </div> <div class="profile-about"> <a href="https://tech.excite.co.jp/about">このブログについて</a> </div> </div> </div> <div class="hatena-module hatena-module-authors-list"> <div class="hatena-module-title"> 執筆者リスト </div> <div class="hatena-module-body"> <ul class="hatena-urllist authors-urllist"> <li> <a href="https://tech.excite.co.jp/archive/author/earu"> <img src="https://cdn.profile-image.st-hatena.com/users/earu/profile.png?1616486065" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="earu"> id:earu </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ooo-ka999"> <img src="https://cdn.profile-image.st-hatena.com/users/ooo-ka999/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ooo-ka999"> id:ooo-ka999 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-soichiro-yoshikawa"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-soichiro-yoshikawa/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-soichiro-yoshikawa"> id:excite-soichiro-yoshikawa </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/saitoayumu"> <img src="https://cdn.profile-image.st-hatena.com/users/saitoayumu/profile.png?1660117459" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="saitoayumu"> id:saitoayumu </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-okazi"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-okazi/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-okazi"> id:excite-okazi </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/designsazuka"> <img src="https://cdn.profile-image.st-hatena.com/users/designsazuka/profile.png?1628586026" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="designsazuka"> id:designsazuka </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite_ny"> <img src="https://cdn.profile-image.st-hatena.com/users/excite_ny/profile.png?1644211404" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite_ny"> id:excite_ny </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/KAJIJI_Design"> <img src="https://cdn.profile-image.st-hatena.com/users/KAJIJI_Design/profile.png?1643942925" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="KAJIJI_Design"> id:KAJIJI_Design </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/haruka_sawada"> <img src="https://cdn.profile-image.st-hatena.com/users/haruka_sawada/profile.png?1701311021" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="haruka_sawada"> id:haruka_sawada </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/gifi48skahgohsk"> <img src="https://cdn.profile-image.st-hatena.com/users/gifi48skahgohsk/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="gifi48skahgohsk"> id:gifi48skahgohsk </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-kazuki"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-kazuki/profile.png?1676859520" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-kazuki"> id:excite-kazuki </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/theialye"> <img src="https://cdn.profile-image.st-hatena.com/users/theialye/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="theialye"> id:theialye </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/shimon15"> <img src="https://cdn.profile-image.st-hatena.com/users/shimon15/profile.png?1733463798" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="shimon15"> id:shimon15 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/fuki-lapin"> <img src="https://cdn.profile-image.st-hatena.com/users/fuki-lapin/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="fuki-lapin"> id:fuki-lapin </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/derasato"> <img src="https://cdn.profile-image.st-hatena.com/users/derasato/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="derasato"> id:derasato </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/Rise_Fujii"> <img src="https://cdn.profile-image.st-hatena.com/users/Rise_Fujii/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="Rise_Fujii"> id:Rise_Fujii </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/taesokkwon"> <img src="https://cdn.profile-image.st-hatena.com/users/taesokkwon/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="taesokkwon"> id:taesokkwon </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/AtsukiHattori"> <img src="https://cdn.profile-image.st-hatena.com/users/AtsukiHattori/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="AtsukiHattori"> id:AtsukiHattori </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/HarukiShimizu"> <img src="https://cdn.profile-image.st-hatena.com/users/HarukiShimizu/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="HarukiShimizu"> id:HarukiShimizu </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/nomura_ex"> <img src="https://cdn.profile-image.st-hatena.com/users/nomura_ex/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="nomura_ex"> id:nomura_ex </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/aya_excite"> <img src="https://cdn.profile-image.st-hatena.com/users/aya_excite/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="aya_excite"> id:aya_excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/taanatsu"> <img src="https://cdn.profile-image.st-hatena.com/users/taanatsu/profile.png?1669107656" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="taanatsu"> id:taanatsu </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kikuIntern"> <img src="https://cdn.profile-image.st-hatena.com/users/kikuIntern/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kikuIntern"> id:kikuIntern </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/WatanabeNeo"> <img src="https://cdn.profile-image.st-hatena.com/users/WatanabeNeo/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="WatanabeNeo"> id:WatanabeNeo </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/naoto_nn"> <img src="https://cdn.profile-image.st-hatena.com/users/naoto_nn/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="naoto_nn"> id:naoto_nn </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/suto145"> <img src="https://cdn.profile-image.st-hatena.com/users/suto145/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="suto145"> id:suto145 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/harumaki_exciteintern"> <img src="https://cdn.profile-image.st-hatena.com/users/harumaki_exciteintern/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="harumaki_exciteintern"> id:harumaki_exciteintern </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/shunsuke0713"> <img src="https://cdn.profile-image.st-hatena.com/users/shunsuke0713/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="shunsuke0713"> id:shunsuke0713 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kona_excite"> <img src="https://cdn.profile-image.st-hatena.com/users/kona_excite/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kona_excite"> id:kona_excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ketakata-excite"> <img src="https://cdn.profile-image.st-hatena.com/users/ketakata-excite/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ketakata-excite"> id:ketakata-excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/katsuhiro-ito"> <img src="https://cdn.profile-image.st-hatena.com/users/katsuhiro-ito/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="katsuhiro-ito"> id:katsuhiro-ito </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-mthiroshi"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-mthiroshi/profile.png?1670480112" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-mthiroshi"> id:excite-mthiroshi </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/nogu626-excite"> <img src="https://cdn.profile-image.st-hatena.com/users/nogu626-excite/profile.png?1660487692" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="nogu626-excite"> id:nogu626-excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-takayuki-miura"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-takayuki-miura/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-takayuki-miura"> id:excite-takayuki-miura </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/nori420-excite"> <img src="https://cdn.profile-image.st-hatena.com/users/nori420-excite/profile.png?1657013146" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="nori420-excite"> id:nori420-excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/mami_kawasaki"> <img src="https://cdn.profile-image.st-hatena.com/users/mami_kawasaki/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="mami_kawasaki"> id:mami_kawasaki </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/e125731"> <img src="https://cdn.profile-image.st-hatena.com/users/e125731/profile.png?1508129643" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="e125731"> id:e125731 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/yuri-sakai"> <img src="https://cdn.profile-image.st-hatena.com/users/yuri-sakai/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="yuri-sakai"> id:yuri-sakai </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-at-ma"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-at-ma/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-at-ma"> id:excite-at-ma </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ex-mii"> <img src="https://cdn.profile-image.st-hatena.com/users/ex-mii/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ex-mii"> id:ex-mii </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/trima"> <img src="https://cdn.profile-image.st-hatena.com/users/trima/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="trima"> id:trima </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/exRyusei1026"> <img src="https://cdn.profile-image.st-hatena.com/users/exRyusei1026/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="exRyusei1026"> id:exRyusei1026 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/honyafan"> <img src="https://cdn.profile-image.st-hatena.com/users/honyafan/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="honyafan"> id:honyafan </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-osawa"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-osawa/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-osawa"> id:excite-osawa </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/aSano"> <img src="https://cdn.profile-image.st-hatena.com/users/aSano/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="aSano"> id:aSano </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/designtoshima"> <img src="https://cdn.profile-image.st-hatena.com/users/designtoshima/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="designtoshima"> id:designtoshima </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kugue"> <img src="https://cdn.profile-image.st-hatena.com/users/kugue/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kugue"> id:kugue </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/shuheioka0101"> <img src="https://cdn.profile-image.st-hatena.com/users/shuheioka0101/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="shuheioka0101"> id:shuheioka0101 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/yuto0313"> <img src="https://cdn.profile-image.st-hatena.com/users/yuto0313/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="yuto0313"> id:yuto0313 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/masakiarai--excite"> <img src="https://cdn.profile-image.st-hatena.com/users/masakiarai--excite/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="masakiarai--excite"> id:masakiarai--excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-kaneko"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-kaneko/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-kaneko"> id:excite-kaneko </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-haruki"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-haruki/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-haruki"> id:excite-haruki </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/shutaro_sato"> <img src="https://cdn.profile-image.st-hatena.com/users/shutaro_sato/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="shutaro_sato"> id:shutaro_sato </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/yatech"> <img src="https://cdn.profile-image.st-hatena.com/users/yatech/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="yatech"> id:yatech </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/KiyohiroMurai"> <img src="https://cdn.profile-image.st-hatena.com/users/KiyohiroMurai/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="KiyohiroMurai"> id:KiyohiroMurai </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kei_kono23"> <img src="https://cdn.profile-image.st-hatena.com/users/kei_kono23/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kei_kono23"> id:kei_kono23 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/subekashi"> <img src="https://cdn.profile-image.st-hatena.com/users/subekashi/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="subekashi"> id:subekashi </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kotamemiya"> <img src="https://cdn.profile-image.st-hatena.com/users/kotamemiya/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kotamemiya"> id:kotamemiya </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/e_riou_iwata"> <img src="https://cdn.profile-image.st-hatena.com/users/e_riou_iwata/profile.png?1692264011" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="e_riou_iwata"> id:e_riou_iwata </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-naka-sho"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-naka-sho/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-naka-sho"> id:excite-naka-sho </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-SD"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-SD/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-SD"> id:excite-SD </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/akaringo_1117"> <img src="https://cdn.profile-image.st-hatena.com/users/akaringo_1117/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="akaringo_1117"> id:akaringo_1117 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excitecausasui"> <img src="https://cdn.profile-image.st-hatena.com/users/excitecausasui/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excitecausasui"> id:excitecausasui </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/hibikiosawa4388"> <img src="https://cdn.profile-image.st-hatena.com/users/hibikiosawa4388/profile.png?1618216676" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="hibikiosawa4388"> id:hibikiosawa4388 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-takarada"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-takarada/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-takarada"> id:excite-takarada </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite_sugama"> <img src="https://cdn.profile-image.st-hatena.com/users/excite_sugama/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite_sugama"> id:excite_sugama </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/e-yamaguchi"> <img src="https://cdn.profile-image.st-hatena.com/users/e-yamaguchi/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="e-yamaguchi"> id:e-yamaguchi </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ebi_j"> <img src="https://cdn.profile-image.st-hatena.com/users/ebi_j/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ebi_j"> id:ebi_j </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/pomupomupurinkun"> <img src="https://cdn.profile-image.st-hatena.com/users/pomupomupurinkun/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="pomupomupurinkun"> id:pomupomupurinkun </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/chouette11"> <img src="https://cdn.profile-image.st-hatena.com/users/chouette11/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="chouette11"> id:chouette11 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/nonchan06"> <img src="https://cdn.profile-image.st-hatena.com/users/nonchan06/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="nonchan06"> id:nonchan06 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite_seya"> <img src="https://cdn.profile-image.st-hatena.com/users/excite_seya/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite_seya"> id:excite_seya </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/yukino_sasaki_excite"> <img src="https://cdn.profile-image.st-hatena.com/users/yukino_sasaki_excite/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="yukino_sasaki_excite"> id:yukino_sasaki_excite </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/kazuya1177"> <img src="https://cdn.profile-image.st-hatena.com/users/kazuya1177/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="kazuya1177"> id:kazuya1177 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/naty44"> <img src="https://cdn.profile-image.st-hatena.com/users/naty44/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="naty44"> id:naty44 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/miya-watanabe"> <img src="https://cdn.profile-image.st-hatena.com/users/miya-watanabe/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="miya-watanabe"> id:miya-watanabe </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/itomoto0312"> <img src="https://cdn.profile-image.st-hatena.com/users/itomoto0312/profile.png?1626158524" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="itomoto0312"> id:itomoto0312 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/e_yamauchi_hiroki"> <img src="https://cdn.profile-image.st-hatena.com/users/e_yamauchi_hiroki/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="e_yamauchi_hiroki"> id:e_yamauchi_hiroki </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ixit_someya"> <img src="https://cdn.profile-image.st-hatena.com/users/ixit_someya/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ixit_someya"> id:ixit_someya </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite_nishiba"> <img src="https://cdn.profile-image.st-hatena.com/users/excite_nishiba/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite_nishiba"> id:excite_nishiba </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-ohshige"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-ohshige/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-ohshige"> id:excite-ohshige </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/moriwaki111"> <img src="https://cdn.profile-image.st-hatena.com/users/moriwaki111/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="moriwaki111"> id:moriwaki111 </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ixit_kona"> <img src="https://cdn.profile-image.st-hatena.com/users/ixit_kona/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ixit_kona"> id:ixit_kona </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ixit_hori"> <img src="https://cdn.profile-image.st-hatena.com/users/ixit_hori/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ixit_hori"> id:ixit_hori </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ixit-okazaki"> <img src="https://cdn.profile-image.st-hatena.com/users/ixit-okazaki/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ixit-okazaki"> id:ixit-okazaki </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/ixit-kata"> <img src="https://cdn.profile-image.st-hatena.com/users/ixit-kata/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="ixit-kata"> id:ixit-kata </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/b_murabito"> <img src="https://cdn.profile-image.st-hatena.com/users/b_murabito/profile.png?1568204545" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="b_murabito"> id:b_murabito </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excitech"> <img src="https://cdn.profile-image.st-hatena.com/users/excitech/profile.png?1620728663" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excitech"> id:excitech </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/Ryutaro_isekiii"> <img src="https://cdn.profile-image.st-hatena.com/users/Ryutaro_isekiii/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="Ryutaro_isekiii"> id:Ryutaro_isekiii </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/takeshi_fujita"> <img src="https://cdn.profile-image.st-hatena.com/users/takeshi_fujita/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="takeshi_fujita"> id:takeshi_fujita </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite-otsu"> <img src="https://cdn.profile-image.st-hatena.com/users/excite-otsu/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite-otsu"> id:excite-otsu </span> </a> </li> <li> <a href="https://tech.excite.co.jp/archive/author/excite_yuchiro22"> <img src="https://cdn.profile-image.st-hatena.com/users/excite_yuchiro22/profile.png" class="authors-user-icon"> <span class="authors-user-name" data-load-nickname="1" data-user-name="excite_yuchiro22"> id:excite_yuchiro22 </span> </a> </li> </ul> </div> </div> </div> </aside> </div> </div> </div> </div> <script async src="https://s.hatena.ne.jp/js/widget/star.js"></script> <script> if (typeof window.Hatena === 'undefined') { window.Hatena = {}; } if (!Hatena.hasOwnProperty('Star')) { Hatena.Star = { VERSION: 2, }; } </script> <div id="fb-root"></div> <script>(function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&appId=719729204785177&version=v17.0"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <div class="quote-box"> <div class="tooltip-quote tooltip-quote-stock"> <i class="blogicon-quote" title="引用をストック"></i> </div> <div class="tooltip-quote tooltip-quote-tweet js-tooltip-quote-tweet"> <a class="js-tweet-quote" target="_blank" data-track-name="quote-tweet" data-track-once> <img src="https://cdn.blog.st-hatena.com/images/admin/quote/quote-x-icon.svg?version=15b44d6f3386d0946efd031c34207d" title="引用して投稿する" > </a> </div> </div> <div class="quote-stock-panel" id="quote-stock-message-box" style="position: absolute; z-index: 3000"> <div class="message-box" id="quote-stock-succeeded-message" style="display: none"> <p>引用をストックしました</p> <button class="btn btn-primary" id="quote-stock-show-editor-button" data-track-name="curation-quote-edit-button">ストック一覧を見る</button> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="message-box" id="quote-login-required-message" style="display: none"> <p>引用するにはまずログインしてください</p> <button class="btn btn-primary" id="quote-login-button">ログイン</button> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="error-box" id="quote-stock-failed-message" style="display: none"> <p>引用をストックできませんでした。再度お試しください</p> <button class="btn quote-stock-close-message-button">閉じる</button> </div> <div class="error-box" id="unstockable-quote-message-box" style="display: none; position: absolute; z-index: 3000;"> <p>限定公開記事のため引用できません。</p> </div> </div> <script type="x-underscore-template" id="js-requote-button-template"> <div class="requote-button js-requote-button"> <button class="requote-button-btn tipsy-top" title="引用する"><i class="blogicon-quote"></i></button> </div> </script> <div id="hidden-subscribe-button" style="display: none;"> <div class="hatena-follow-button-box btn-subscribe js-hatena-follow-button-box" > <a href="#" class="hatena-follow-button js-hatena-follow-button"> <span class="subscribing"> <span class="foreground">読者です</span> <span class="background">読者をやめる</span> </span> <span class="unsubscribing" data-track-name="profile-widget-subscribe-button" data-track-once> <span class="foreground">読者になる</span> <span class="background">読者になる</span> </span> </a> <div class="subscription-count-box js-subscription-count-box"> <i></i> <u></u> <span class="subscription-count js-subscription-count"> </span> </div> </div> </div> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <script src="https://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/jquery.min.js?v=1.12.4&version=15b44d6f3386d0946efd031c34207d"></script> <script src="https://cdn.blog.st-hatena.com/js/texts-ja.js?version=15b44d6f3386d0946efd031c34207d"></script> <script id="vendors-js" data-env="production" src="https://cdn.blog.st-hatena.com/js/vendors.js?version=15b44d6f3386d0946efd031c34207d" crossorigin="anonymous"></script> <script id="hatenablog-js" data-env="production" src="https://cdn.blog.st-hatena.com/js/hatenablog.js?version=15b44d6f3386d0946efd031c34207d" crossorigin="anonymous" data-page-id="index"></script> <script>Hatena.Diary.GlobalHeader.init()</script> </body> </html>