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

<channel>
	<title>Firebase | みんたく</title>
	<atom:link href="https://mintaku-blog.net/category/develop/firebase/feed/" rel="self" type="application/rss+xml" />
	<link>https://mintaku-blog.net</link>
	<description>みんたくの技術ブログ</description>
	<lastBuildDate>Thu, 28 Mar 2024 17:28:15 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.11</generator>

<image>
	<url>https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2018/06/cropped-ipad-820272_640.jpg?fit=32%2C32&#038;ssl=1</url>
	<title>Firebase | みんたく</title>
	<link>https://mintaku-blog.net</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">144480658</site>	<item>
		<title>実行環境によってFirebase環境の接続を切り替える</title>
		<link>https://mintaku-blog.net/firebase-dev/</link>
					<comments>https://mintaku-blog.net/firebase-dev/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Wed, 06 Dec 2023 04:26:18 +0000</pubDate>
				<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2434</guid>

					<description><![CDATA[<p>今更ながらFirebaseの開発環境を用意して、実行環境によってアプリケーションとの接続を切り替えるようにしたので、メモ代わりに書きました。 開発環境のFi …</p>
The post <a href="https://mintaku-blog.net/firebase-dev/">実行環境によってFirebase環境の接続を切り替える</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>今更ながらFirebaseの開発環境を用意して、実行環境によってアプリケーションとの接続を切り替えるようにしたので、メモ代わりに書きました。</p>
<p>開発環境のFirebaseを構築し、アプリケーション側を設定してきます。今回の目的として、ローカルでは開発環境のFirebaseに接続するようにし、本番では本番環境のFirebaseに接続するようにします。</p>
<h2>実行環境によってFirebaseとの接続を切り替えるアプリケーション側の設定</h2>
<p>CSR・SSRどちらでも利用したいものはpublicで、サーバーサイドのみ利用するものはprivateで環境変数をセットします。今回はSSGで、CSRでも利用するためにpublicで設定しています。</p>
<p>ここでは本番環境の値を定義しています。</p>
<p>・nuxt.config.ts</p><pre class="crayon-plain-tag">...

runtimeConfig: {
  public: {
    fbApiKey: 'xxxxxxxxxxxx',
    fbAuthDomain: 'xxxxxxxxxxxx',
    fbProjectId: 'xxxxxxxxxxxx',
    fbStorageBucket: 'xxxxxxxxxxxx',
    fbMessagingSenderId: 'xxxxxxxxxxxx',
    fbAppId: 'xxxxxxxxxxxx',
    fbMesurementId: 'xxxxxxxxxxxx'
  }
},

...</pre><p>&nbsp;</p>
<p>ローカル用の環境変数を用意して、そこに開発環境用のFirebase接続情報を定義しています。</p>
<p>NUXT_という接頭辞をつけると、ランタイム構成の環境変数を上書きできます。今回はpublic配下の環境変数を上書きするのNUXT_PUBLIC_の接頭辞をつけています。</p>
<p>・.env.local</p><pre class="crayon-plain-tag">NUXT_PUBLIC_FB_API_KEY=xxxxxxxxxxxx
NUXT_PUBLIC_FB_AUTH_DOMAIN=xxxxxxxxxxxx
NUXT_PUBLIC_FB_PROJECT_ID=xxxxxxxxxxxx
NUXT_PUBLIC_FB_STORAGE_BUCKET=xxxxxxxxxxxx
NUXT_PUBLIC_FB_MESSAGING_SENDER_ID=xxxxxxxxxxxx
NUXT_PUBLIC_FB_APP_ID=xxxxxxxxxxxx</pre><p>&nbsp;</p>
<p>最後にpackage.jsonの実行コマンドに&#8211;dotenvで.env.localを読み込むようにして完了です。こうすることで、.env.localの値が上書きされて、ローカル起動時にFirebaseの開発環境に接続して開発することができます。</p>
<p>・package.json</p><pre class="crayon-plain-tag">...

"scripts": {
  "dev": "nuxi dev --dotenv .env.local"
},

...</pre><p>参考：</p>
<div class="ys-blog-card__text-link"><a href="https://nuxt.com/docs/guide/going-further/runtime-config" >https://nuxt.com/docs/guide/going-further/runtime-config</a></div>The post <a href="https://mintaku-blog.net/firebase-dev/">実行環境によってFirebase環境の接続を切り替える</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/firebase-dev/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2434</post-id>	</item>
		<item>
		<title>Googleログイン認証をTypeScriptで実装する</title>
		<link>https://mintaku-blog.net/google-auth-ts/</link>
					<comments>https://mintaku-blog.net/google-auth-ts/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Thu, 17 Aug 2023 07:29:54 +0000</pubDate>
				<category><![CDATA[TypeScript]]></category>
		<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2397</guid>

					<description><![CDATA[<p>メモ的な感じでGoogleログイン認証をTypeScriptで実装した内容をまとめます。認証はFirebase Authenticationを使用しています …</p>
The post <a href="https://mintaku-blog.net/google-auth-ts/">Googleログイン認証をTypeScriptで実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>メモ的な感じでGoogleログイン認証をTypeScriptで実装した内容をまとめます。認証はFirebase Authenticationを使用しています。</p>
<h2>UseCase層でGoogle認証のロジックを実装する</h2>
<p>流れとしてはFirebase AuthenticationでGoogle認証した後、DBにユーザー情報を問い合わせます。</p>
<p>ユーザー情報がなかったら新規登録としてユーザ登録します。ユーザーログイン認証完了後はmypageに遷移し、認証情報をstateに登録します。</p><pre class="crayon-plain-tag">async signInWithGoogle(auth: Auth) {
  const userWithGoogle: CredentialUser = await this.authGateway.signInWithGoogle(auth)
  const userAccount: UserAccount | null = await this.userGateway.getUserByUid(userWithGoogle.uid)

  if (userAccount) {
    this.toastPresenter.successToast(MESSAGES.AUTH.LOGIN_SUCCESS)
  } else {
    await this.userGateway.createUser(userWithGoogle)
    this.toastPresenter.successToast(MESSAGES.AUTH.REGISTER_SUCCESS)
  }
  await this.storeAuthState(userWithGoogle.uid)
  this.navigationPresenter.navigateTo('/mypage')
} catch(error: Error) {
  console.error('signInWithGoogle error: ', error);
  this.toastPresenter.errorToast(`${MESSAGES.UNEXPECTED_ERROR}: ${error.message}`)
}

private async storeAuthState(uid: string) {
  const authAccount = await this.userGateway.getUserByUid(uid)
  this.authPresenter.storeAuth(authAccount!)
}ち</pre><p>ちなみにUseCaseのUTはこんな感じになりました。あと登録パターンなどのシナリオがいくつかあるといった感じです。</p>
<p>Clean Architecutureをベースにレイヤリングしているので、テストは書きやすかったです。</p><pre class="crayon-plain-tag">it('Google認証でログインすることができる', async () =&gt; {
  const auth = mock&lt;Auth&gt;();
    
  const signInWithGoogleMock = jest.fn()
  when(signInWithGoogleMock).calledWith(auth).mockReturnValueOnce(credentialUser)
  authGateway.signInWithGoogle = signInWithGoogleMock

  const getUserByUidMock = jest.fn()
  when(getUserByUidMock).calledWith(credentialUser.uid).mockReturnValueOnce(userAccount)
  userGateway.getUserByUid = getUserByUidMock

  const successToastMock = jest.fn()
  when(successToastMock).calledWith(MESSAGES.AUTH.LOGIN_SUCCESS)
  toastPresenter.successToast = successToastMock

  const navigateToMock = jest.fn()
  when(navigateToMock).calledWith('/mypage')
  navigationPresenter.navigateTo = navigateToMock

  await authUsecase.signInWithGoogle(auth);

  expect(authGateway.signInWithGoogle).nthCalledWith(1, auth);
  expect(userGateway.getUserByUid).nthCalledWith(1, credentialUser.uid);
  expect(toastPresenter.successToast).nthCalledWith(1, MESSAGES.AUTH.LOGIN_SUCCESS);
});</pre><p>&nbsp;</p>
<h2>GatewayでDomainに変換する</h2>
<p>UseCaseのthis.authGateway.signInWithGoogle(auth)でFirebase AuthenticationにGoogle認証しているところをピックアップして、Gatewayの実装を取り上げます。</p>
<p>Gatewayとしては、DriverでサードパーティであるFirebase Authenticationを呼び出した返り値を、このアプリケーション内のコアとなるDomainに変換してUseCaseに返します。</p>
<p>つまり、Gatewayが外部とアプリケーションの境界部分を担っている感じです。今回は、CredentialUserというDomainに変換して、UseCaseに返しています。</p><pre class="crayon-plain-tag">async signInWithGoogle(auth: Auth): Promise&lt;CredentialUser&gt; {
  const userCredentialWithGoogle = await this.authDriver.signInWithGoogle(auth)
  return {
    uid: userCredentialWithGoogle.user.uid,
    displayName: userCredentialWithGoogle.user.displayName,
    email: userCredentialWithGoogle.user.email,
    photoURL: userCredentialWithGoogle.user.photoURL,
  } as CredentialUser
}</pre><p>&nbsp;</p>
<h2>Driverで実際にGoogle認証する</h2>
<p>実際にGoogle認証するのはこのDriverになります。DriverではGoogleAuthProvider()からsignInWithPopup()を呼び出し、認証した結果を返します。</p>
<p>本来はDriverで使用するエンティティを用意して、その型に詰めてからGatewayに返してあげる方が、アプリケーションとサードパーティとの切り分けができてより良い気もしますが、一旦ここではそのままGatewayに返しています。</p><pre class="crayon-plain-tag">async signInWithGoogle(auth: Auth): Promise&lt;UserCredential&gt; {
  const provider = new GoogleAuthProvider()
  const userCredentialWithGoogle = await signInWithPopup(auth, provider)
  return userCredentialWithGoogle
}</pre><p>すんごいざっくりですが、メモ的な感じでまとめました。</p>The post <a href="https://mintaku-blog.net/google-auth-ts/">Googleログイン認証をTypeScriptで実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/google-auth-ts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2397</post-id>	</item>
		<item>
		<title>Firebase Hostingでダウンロード量が爆上がりしてたので原因を突き止める</title>
		<link>https://mintaku-blog.net/firebase-hosting-download/</link>
					<comments>https://mintaku-blog.net/firebase-hosting-download/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sat, 03 Jun 2023 01:35:27 +0000</pubDate>
				<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2379</guid>

					<description><![CDATA[<p>Firebase Hostingでダウンロード量が爆上がりしてる。。 NetlifyからFirebase Hostingに移行し、金額的にもNetlifyよ …</p>
The post <a href="https://mintaku-blog.net/firebase-hosting-download/">Firebase Hostingでダウンロード量が爆上がりしてたので原因を突き止める</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<h2>Firebase Hostingでダウンロード量が爆上がりしてる。。</h2>
<p><img data-attachment-id="2383" data-permalink="https://mintaku-blog.net/firebase-hosting-download/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2023-05-29-0-01-12/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?fit=1196%2C504&amp;ssl=1" data-orig-size="1196,504" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2023-05-29 0.01.12" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?fit=300%2C126&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?fit=800%2C338&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?resize=800%2C338&#038;ssl=1" alt="" width="800" height="338" class="aligncenter size-large wp-image-2383" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?resize=1024%2C432&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?resize=300%2C126&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?resize=768%2C324&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/877e13afba9edc6ba19bec1242e951c7.png?w=1196&amp;ssl=1 1196w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>NetlifyからFirebase Hostingに移行し、金額的にもNetlifyより安く抑えられてていい感じだったのですが、最近確認したら、送信バイト数が異常に上がっていることに気づきました。</p>
<p>真っ先に疑ったのが画像でした。と言っても新しく画像を急激に増やしたわけでも高画質にしたわけでもないので、うーんと思いながら調査しました。</p>
<p>画像はContentfulから持ってきているのですが、webpに変換していることもあり、やはりここまでの影響を与えていることはなさそうでした。</p>
<p>次に、このタイミングで認証機能をリリースしたこともあり、その機能にまつわる諸々洗い出していたのですが、さすがにこんなにも急激に送信バイトが増えるものは見当たらず途方に暮れていました。。</p>
<h2>フォントでした</h2>
<p><img data-attachment-id="2382" data-permalink="https://mintaku-blog.net/firebase-hosting-download/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2023-05-25-0-09-43/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?fit=1676%2C110&amp;ssl=1" data-orig-size="1676,110" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2023-05-25 0.09.43" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?fit=300%2C20&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?fit=800%2C52&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?resize=800%2C52&#038;ssl=1" alt="" width="800" height="52" class="aligncenter size-large wp-image-2382" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?resize=1024%2C67&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?resize=300%2C20&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?resize=768%2C50&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?resize=1536%2C101&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?w=1676&amp;ssl=1 1676w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/c72655655e57f6804c9e8a99d97ebbe7.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>当たりが見当たらず、いろんなページを地道にネットワークで調べていたら、こんなにを見つけました。</p>
<p>異常なまでのサイズのデカさのフォントで、明らかに原因となっていそうです。完全にミスですが、フォントをダウンロードする形でFirebase Hostingに格納しており、毎通信でとてつもない転送量になっていました。</p>
<p>といわけで、このフォントを使わないようにして削除してデプロイしたら、案の定、収まりましたとさ。</p>
<p><img data-attachment-id="2381" data-permalink="https://mintaku-blog.net/firebase-hosting-download/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2023-05-28-23-46-05/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?fit=1740%2C572&amp;ssl=1" data-orig-size="1740,572" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2023-05-28 23.46.05" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?fit=300%2C99&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?fit=800%2C263&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?resize=800%2C263&#038;ssl=1" alt="" width="800" height="263" class="aligncenter size-large wp-image-2381" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?resize=1024%2C337&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?resize=300%2C99&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?resize=768%2C252&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?resize=1536%2C505&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?w=1740&amp;ssl=1 1740w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/319b136788b304a27a8cc2f313b027cc.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>めでたしめでたし。</p>The post <a href="https://mintaku-blog.net/firebase-hosting-download/">Firebase Hostingでダウンロード量が爆上がりしてたので原因を突き止める</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/firebase-hosting-download/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2379</post-id>	</item>
		<item>
		<title>改めて運営しているWebサービスのアーキテクチャを整理する</title>
		<link>https://mintaku-blog.net/architecture-2nd/</link>
					<comments>https://mintaku-blog.net/architecture-2nd/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Fri, 19 May 2023 11:35:02 +0000</pubDate>
				<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[Firebase]]></category>
		<category><![CDATA[アーキテクチャ]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2370</guid>

					<description><![CDATA[<p>最近、NetlifyからFirebase Hostingに移行したり、認証機能を追加実装したりとアーキテクチャがちょっと変わってきたので、改めて整理したいと …</p>
The post <a href="https://mintaku-blog.net/architecture-2nd/">改めて運営しているWebサービスのアーキテクチャを整理する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>最近、NetlifyからFirebase Hostingに移行したり、認証機能を追加実装したりとアーキテクチャがちょっと変わってきたので、改めて整理したいと思います。</p>
<h2>今のざっくりアーキテクチャ図</h2>
<p><img data-attachment-id="2373" data-permalink="https://mintaku-blog.net/architecture-2nd/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2023-05-19-11-31-10/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?fit=1418%2C612&amp;ssl=1" data-orig-size="1418,612" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2023-05-19 11.31.10" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?fit=300%2C129&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?fit=800%2C345&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?resize=800%2C345&#038;ssl=1" alt="" width="800" height="345" class="aligncenter size-large wp-image-2373" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?resize=1024%2C442&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?resize=300%2C129&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?resize=768%2C331&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2936e99a19fcbf591587759c99c5011a.jpg?w=1418&amp;ssl=1 1418w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>こちらの記事で書いてありますが、元々Netlifyを使用していましたが、Firebase Hostingに移行しました。</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img width="560" height="315" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?fit=560%2C315&amp;ssl=1" class="attachment-large size-large wp-post-image" alt="" loading="lazy" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?w=560&amp;ssl=1 560w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?resize=300%2C169&amp;ssl=1 300w" sizes="(max-width: 560px) 100vw, 560px" data-attachment-id="2363" data-permalink="https://mintaku-blog.net/netlify-to-firebase/help-raise-heart-disease-awareness%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc%e3%81%ae%e3%82%b3%e3%83%92%e3%82%9a%e3%83%bc-9-5/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?fit=560%2C315&amp;ssl=1" data-orig-size="560,315" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Help raise Heart Disease Awarenessのコピーのコピーのコピー (9)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/04/afbf9bcde7cb172c4c5e6bc2b6e660b9.png?fit=560%2C315&amp;ssl=1" />			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://mintaku-blog.net/netlify-to-firebase/">NetlifyからFirebase Hostingに移行した話</a>
			</p>
							<div class="ys-blog-card__dscr">
					満を辞して？NetlifyからFirebase Hostingに移行したので、メ&hellip;				</div>
										<div class="ys-blog-card__domain">mintaku-blog.net</div>
					</div>
	</div>
</div>

<p>基本SSGの構成は変わっておらず、後で説明しますが、一部SPA的なページが追加されました。</p>
<p>流れとしてはmainブランチにプッシュされるとGithub Actionsのパイプラインが動き出し、ビルドが始まります。</p>
<p>ビルド時はContenfulからデータを取得して、静的なページを生成していきます。ページの生成が完了すると、Firebase Hostingにデプロイします。</p>
<p>ページ数は今のところ大体3300ページくらいあり、ビルドしてデプロイするまで20分近くかかっており、長すぎるビルド時間が直近の課題となっています。</p>
<p>今考えているアプローチ方法は、ISGの導入や最近Nuxt3機能追加されたpayloadを使うなど、サーバー通信を減らしていきたいと思っています。</p>
<p>&nbsp;</p>
<h2>認証機能のところ</h2>
<p><img data-attachment-id="2374" data-permalink="https://mintaku-blog.net/architecture-2nd/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2023-05-19-19-38-33/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?fit=1366%2C910&amp;ssl=1" data-orig-size="1366,910" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2023-05-19 19.38.33" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?fit=800%2C533&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?resize=800%2C533&#038;ssl=1" alt="" width="800" height="533" class="aligncenter size-large wp-image-2374" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?resize=1024%2C682&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?resize=768%2C512&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2023/05/2801c7ada334840d3ce2e425ae897010.jpg?w=1366&amp;ssl=1 1366w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>今回新しく追加した認証機能はFirebase Authenticationを使いました。Firebaseに統合したいのもあって、ホスティングサービスもNetlifyからFirebase Hostingに移行した感じです。</p>
<p>1stリリースでは、認証のプロバイダはGoogleとTwitterのみにしています。後々、メールアドレスや他のSNS認証も追加する予定です。</p>
<p>ユーザー情報などの格納データはFirestoreを使っています。Firebase Authenticationとあわせて開発しやすかったです。</p>
<p>&nbsp;</p>
<h2>今後のアーキテクチャについて</h2>
<p>今後の開発において、ますます動的な機能が増えていくと思います。今はサーバーレスのSSGで開発していますが、後々はAPIを立てたり、SSRに変更するなど考えています。</p>
<p>新しい技術なども出てきたりすると思うので、臨機応変にその時その時考えて、アーキテクチャや使用技術を決めていきたいと思います。</p>
<p>また、負債を残さないためにもdependabotの導入によるライブラリのバージョンアップやこまめなリファクタリングなど、開発生産性を高く保ちながらより良いサービスを提供していきたいです。</p>The post <a href="https://mintaku-blog.net/architecture-2nd/">改めて運営しているWebサービスのアーキテクチャを整理する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/architecture-2nd/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2370</post-id>	</item>
		<item>
		<title>NetlifyからFirebase Hostingに移行した話</title>
		<link>https://mintaku-blog.net/netlify-to-firebase/</link>
					<comments>https://mintaku-blog.net/netlify-to-firebase/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Fri, 21 Apr 2023 15:32:14 +0000</pubDate>
				<category><![CDATA[Firebase]]></category>
		<category><![CDATA[個人開発]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2360</guid>

					<description><![CDATA[<p>満を辞して？NetlifyからFirebase Hostingに移行したので、メモっておきます。 なぜNetlifyからFirebase Hostingに移 …</p>
The post <a href="https://mintaku-blog.net/netlify-to-firebase/">NetlifyからFirebase Hostingに移行した話</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>満を辞して？NetlifyからFirebase Hostingに移行したので、メモっておきます。</p>
<h2>なぜNetlifyからFirebase Hostingに移行したのか</h2>
<h3>Firebaseに集約したい</h3>
<p>最近認証機能を実装したことから、Firebase AuthenticationやFirestoreを利用することになり、ホスティングサービスもFirebaseにまとめたい感がありました。</p>
<p>今後もFirebaseのサービスを使っていきたいと考えていたので、早いタイミングで集約していこうと思いました。</p>
<h3>コストが安くなる</h3>
<p>Netlifyの1ヶ月のビルド時間の無料枠は300分で、追加500分ごとに7ドルという料金体系でした。</p>
<p>最初は無料枠でも問題なくビルドできていましたが、コンテンツが増えて無料枠で収まることがなくなり、毎月追加枠を2つ購入していた感じになっていました。</p>
<p>そもそもビルド時間長すぎ問題があり、そちらも解決する必要があるのですが、今後もコンテンツが増えていくことを考えると地味にコストが効いてくるなと思っていました。</p>
<p>そこで色々調べていくとFirebase HostingとGitHub Actionsを組みわせた場合、無料枠は2000分とコストがかからずビルドできることを知り、移行したいと思う大きな要因となりました。</p>
<h3>CDNのエッジが国内にあるので早くなるのでは</h3>
<p>Netlifyの無料プランだと日本のエッジノードが利用できず海外からの配信になっているようで、Page Speed Insightsでチェックすると結構遅いことがわかりました。</p>
<p>Firebase Hostingなら国内からの配信になるようなので、サイトのパフォーマンスも向上すると考えていました。</p>
<p>&nbsp;</p>
<h2>NetlifyからFirebase Hostingへの移行作業</h2>
<h3>ローカルからFirebase Hostingにデプロイできるようにする</h3>
<p>まずは公式を参考にして、ローカルでgenerateしたファイルをFirebase Hostingにデプロイできるようにしました。</p>
<p>デプロイしてみてFirebase Hostingにデフォルトで使われているドメインにアクセスしてみて、ページに問題ないかを確認しました。</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://firebase.google.com/docs/hosting/quickstart?hl=ja">Firebase Hosting を使ってみる</a>
			</p>
							<div class="ys-blog-card__dscr">
					Firebase Hosting を始めるためのガイド。Firebase CLI&hellip;				</div>
										<div class="ys-blog-card__domain">firebase.google.com</div>
					</div>
	</div>
</div>

<h3>GitHub Actionsでパイプラインを構築する</h3>
<p>パイプラインを構築するといってもfirebase initするといい感じのymlファイルを生成してくれるので、それを元にちょっと変更しただけでした。</p>
<p>出来上がったパイプラインのymlファイルはこんな感じになりました。ちなみにworkflow_dispatchを追加するとGitHub Actionsから手動でトリガーできるようになります。</p>
<p>特にソースコード変更せずにコンテンツの更新などでビルドしたい場合に役立ちます。</p>
<div>
<div>
<pre class="crayon-plain-tag">name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
  workflow_dispatch:
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm i &amp;&amp; npm run generate
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_HOGE }}'
          channelId: live
          projectId: hoge</pre>
</div>
</div>
<h3>Firebase Hostingにドメインを設定する</h3>
<p>この時点でGitHubにPushするとNetlifyとFirebase Hosting両方にデプロイされるようになっている状態です。</p>
<p>あとは独自ドメインをFirebase Hostingの方に設定するだけです。これも公式を元にポチポチ設定していけば完了します。</p>
<p>設定してから反映まで数時間かかったので、移行日が決まっている場合は、あらかじめ考慮しておくと良いでしょう。</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://firebase.google.com/docs/hosting/custom-domain?hl=ja">カスタム ドメインを接続する &nbsp;|&nbsp; Firebase Hosting</a>
			</p>
							<div class="ys-blog-card__dscr">
					カスタム ドメインを Firebase Hosting サイトに接続するためのガ&hellip;				</div>
										<div class="ys-blog-card__domain">firebase.google.com</div>
					</div>
	</div>
</div>

<p>&nbsp;</p>
<h2>NetlifyからFirebase Hostingに移行する際に注意すること</h2>
<p>完全に忘れていたのですが、NetlifyはURLのパスを大文字から小文字に自動で変換していました。</p>
<p>ヘッドレスCMSとしてContentfulを使用しているのですが、そこのIDは自動で大文字小文字などが混ざった文字列を生成しており、それをURLのパスとして利用していました。</p>
<p>そのため、Google検索などからアクセスした際にURLのパスが小文字に変換された文字列になっており、Firebase Hostingに移行した際にFirebaseは小文字変換はしないため、存在しないページとして認識されてしまっていました。</p>
<h3>リダイレクト処理する</h3>
<p>ということで急いで対応しなければならず、一旦Netlifyに切り戻しを行いつつ、対策を考えました。</p>
<p>パッと思いついたのは小文字のパスでアクセスされた際にちゃんと元々の大文字が含まれたパスにリダイレクトする処理を入れることを思いつきました。</p>
<p>ビルドしているログには大文字が含まれたパスで生成されているのが確認できたので、それらを元にサクッとPythonでいい感じにFirebase.jsonのredirectsにそのままそのまま貼っつけられるような処理を書きました。</p><pre class="crayon-plain-tag">url_list = [ここに元々のURLを書く]

mappings = []

for url in url_list:
  source = url.lower()
  destination = url
  mappings.append({
    "source": source,
    "destination": destination,
    "type": 301
  })

print(mappings)</pre><p>printした結果をfirebase.jsonに貼ってPushしてなんとかことなきを得ました。</p>
<p>NetlifyからFirebase Hostingに移行する際に結構ありがちな失敗かと思うので、参考になればと思います。</p>The post <a href="https://mintaku-blog.net/netlify-to-firebase/">NetlifyからFirebase Hostingに移行した話</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/netlify-to-firebase/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2360</post-id>	</item>
		<item>
		<title>Nuxt3 + Firebase Authenticationで認証機能を実装する</title>
		<link>https://mintaku-blog.net/nuxt3-firebase/</link>
					<comments>https://mintaku-blog.net/nuxt3-firebase/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Wed, 04 Jan 2023 07:53:31 +0000</pubDate>
				<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2316</guid>

					<description><![CDATA[<p>前提の話 Firebaseの初期化の設定などはここでは省略します。今回はGoogleアカウントでの認証を例にメモ的な感じでまとめています。 実装としてはGo …</p>
The post <a href="https://mintaku-blog.net/nuxt3-firebase/">Nuxt3 + Firebase Authenticationで認証機能を実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<h2>前提の話</h2>
<p>Firebaseの初期化の設定などはここでは省略します。今回はGoogleアカウントでの認証を例にメモ的な感じでまとめています。</p>
<p>実装としてはGoogleアカウントで認証し、登録情報をFirestoreに登録するといった流れです。</p>
<p>NuxtとFirebaseのバージョンは以下の通りです。</p>
<ul>
<li>Nuxt: 3.0.0</li>
<li>Firebase: 9.15.0</li>
</ul>
<p>&nbsp;</p>
<h2>Googleアカウントで認証する</h2>
<p>Googleアカウントで登録する流れを実装していきます。</p>
<p>まずは登録するための画面をcomponentsに作成します。このコンポーネントはpagesで呼び出すイメージです。</p>
<p>「Googleで登録」のボタンを押下するとgoogleSignUpメソッドが呼び出されます。ここでのuseAuth()とgoogleSignUpメソッドは後ほど実装していきます。</p>
<p>・components/SignUpForm.vue</p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;div class="signup-form"&gt;
    &lt;p class="title"&gt;SNSアカウントで登録&lt;/p&gt;
    &lt;ul class="signup-buttons"&gt;
      &lt;li&gt;&lt;button class="google-button" @click="googleSignUp"&gt;&lt;i&gt;&lt;GoogleLogo&gt;&lt;/GoogleLogo&gt;&lt;/i&gt;&lt;span&gt;Googleで登録&lt;/span&gt;&lt;/button&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/template&gt;
 
&lt;script setup lang="ts"&gt;
  import GoogleLogo from '@/assets/svg/icons/google-logo.svg'
  const googleSignUp = () =&gt; {
    const { googleSignUp } = useAuth()
    googleSignUp()
  }
&lt;/script&gt;</pre><p>&nbsp;</p>
<p>「Googleで登録」のボタンを押下したgoogleSignUpメソッドがここで呼ばれます。GoogleAuthProviderのインスタンスを作成し、signInWithPopupを呼び出すことで、Google認証にポップアップが表示されます。</p>
<p>Google認証が完了したら、そのデータを元にgetUserメソッドでFirestoreのデータを取得しにいきます。</p>
<p>既にFirestoreにデータがある場合は、認証完了済みなのでログイン画面に遷移させます。データがない場合は新規登録なので、新しくFirestoreにuserデータを登録し、mypage画面に遷移させます。</p>
<p>最後にupdateUserメソッドを呼び出し、useState() を使ってuserの状態管理をしています。alertのところはいい感じのポップアップかトーストを実装したいところです。</p>
<p>・composables/useAuth.ts</p><pre class="crayon-plain-tag">import {
  Auth,
  User,
  UserCredential,
  onAuthStateChanged,
  signInWithPopup,
  GoogleAuthProvider,
} from "firebase/auth";
import { computed, ref } from "vue";
import {
  collection,
  where,
  query,
  getDocs,
  addDoc
} from '@firebase/firestore';
 
export function useAuth() {
  const { $auth, $firestore } = useNuxtApp()
  const user = ref&lt;User | null&gt;($auth.currentUser);
  const isAuthed = computed(() =&gt; !!user.value);
  const db = $firestore;
 
  $auth.onIdTokenChanged((authUser) =&gt; (user.value = authUser));
 
  // ユーザー情報取得
  const getUser = async (uid: string): Promise&lt;any&gt; =&gt; {
    const q = query(
      collection(db, 'users'),
      where("uid", "==", uid)
    );
    const querySnapshot = await getDocs(q);
 
    return querySnapshot.docs[0]
  };
 
  // ユーザー作成
  const createUser = async (user: UserCredential) =&gt; {
    await addDoc(collection(db, 'users'), {
      uid: user.user.uid,
      name: user.user.displayName,
      email: user.user.email,
      photo: user.user.photoURL
    })
  };
 
  // Google新規登録
  async function googleSignUp() {
    try {
      const provider = new GoogleAuthProvider();
      const googleUser = await signInWithPopup($auth, provider);
      const user = await getUser(googleUser.user.uid)
      const { updateUser } = await useUser()
      if (user) {
        alert("既にユーザー登録されています。")
        updateUser(user.data())
        navigateTo("/login", { replace: true })
      } else {
        alert("新規登録完了しました")
        await createUser(googleUser)
        const user = await getUser(googleUser.user.uid)
        updateUser(user.data())
        navigateTo("/mypage", { replace: true })
      }
    } catch (error) {
      throw error;
    }
  }
 
  async function currentUser() {
    return $auth.currentUser
  }
 
  return { isAuthed, user, checkAuthState, googleSignUp, currentUser };
}</pre><p>&nbsp;</p>
<p>updateUserメソッドを使ってuserのstateの状態を管理しています。</p>
<p>認証後はuserのstateにUser情報を入れ、ログアウトなどの認証情報が破棄された場合はnullを入れます。</p>
<p>・composables/useUser.ts</p><pre class="crayon-plain-tag">import { User } from "firebase/auth"
import { Ref } from "vue"

export const useUser = () =&gt; {
  const user: Ref&lt;User | null&gt; = useState&lt;User | null&gt;('user')

  const updateUser = (user: Ref&lt;User | null&gt;) =&gt; (value: User | null) =&gt; {
    user.value = value
  }

  return {
    user: readonly(user),
    updateUser: updateUser(user)
  }
}</pre><p>&nbsp;</p>
<h2>認証状態のチェック</h2>
<p>認証後のマイページに認証されていない場合はアクセスできないようにしたい場合、認証状態をチェックするなどの処理が必要になります。</p>
<p>その場合はmiddlewareで認証状態をチェックし、認証されていない場合はログインページに遷移させるなどの処理を実装します。</p>
<p>definePageMetaで指定してあげることで、このmiddlewareの処理が実行されます。</p>
<p>また、ログイン済みの場合にログインページを表示する場合は、マイページに遷移させるなどの処理も必要になるかと思います。</p>
<p>・middleware/auth.global.ts</p><pre class="crayon-plain-tag">export default defineNuxtRouteMiddleware(async () =&gt; {
  if (!process.server) {
    const { isAuthed, checkAuthState } = useAuth();
    await checkAuthState();

    if (!isAuthed.value) {
      window.location.href = "/login";
    }
  }
});</pre><p>&nbsp;</p>
<p>全てのルートで実行したい処理がある場合は、Nuxt3のグローバルミドルウェアを使うことで実現できます。</p>
<p>.globalが付いたファイルは呼び出し処理を書かずに全てのルートで実行されます。</p>
<p>・middleware/auth-check.global.ts</p><pre class="crayon-plain-tag">export default defineNuxtRouteMiddleware(async () =&gt; {
  if (!process.server) {
    const { checkAuthState } = useAuth();
    await checkAuthState();
  }
})</pre><p></p>The post <a href="https://mintaku-blog.net/nuxt3-firebase/">Nuxt3 + Firebase Authenticationで認証機能を実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/nuxt3-firebase/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2316</post-id>	</item>
		<item>
		<title>Firebase + Nuxt.jsを使ったGoogleアカウント認証を実装する</title>
		<link>https://mintaku-blog.net/firebase-google/</link>
					<comments>https://mintaku-blog.net/firebase-google/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 17 Nov 2020 08:28:21 +0000</pubDate>
				<category><![CDATA[Firebase]]></category>
		<category><![CDATA[Nuxt.js]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1728</guid>

					<description><![CDATA[<p>Firebaseを使ったGoogleアカウントのユーザー認証についてまとめました。認証はFirebase Authentication、画像のストレージはF …</p>
The post <a href="https://mintaku-blog.net/firebase-google/">Firebase + Nuxt.jsを使ったGoogleアカウント認証を実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Firebaseを使ったGoogleアカウントのユーザー認証についてまとめました。認証はFirebase Authentication、画像のストレージはFirebase Storage、ユーザ情報はFirebase Firestoreを使っています。</p>
<h2>事前準備</h2>
<p>認証をする前にfirebase.jsに以下の設定を書いて、Firebaseプロジェクトと連携しておきます。</p>
<p>・firebase.js</p><pre class="crayon-plain-tag">import firebase from 'firebase';

// Firebaseプロジェクトの設定
if (!firebase.apps.length) {
  firebase.initializeApp({
    apiKey: '{ウェブAPIキー}',
    authDomain: '{プロジェクトID}.firebaseapp.com',
    databaseURL: 'https://{プロジェクトID}.firebaseio.com',
    projectId: '{プロジェクトID}',
    storageBucket: '{プロジェクトID}.appspot.com',
    messagingSenderId: '1234567890' // cloudmessagingを使う場合は設定
  })
}

export const firestore = firebase.firestore();
export const storage = firebase.storage();</pre><p>&nbsp;</p>
<h2>Google認証全体像</h2>
<p>Firebase Authenticationを使ったGoogle認証の全体像は以下のようになります。こちらのコードは以下を参考にしています。</p>
<p>参考：<a href="https://github.com/FujiyamaYuta/nuxt-firebase-project" target="_blank" rel="noopener noreferrer">https://github.com/FujiyamaYuta/nuxt-firebase-project</a></p>
<p>それぞれの処理をより深く理解するために、1つ1つの処理を追って自分の言葉で解説していきます。</p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;b-modal :active.sync="isLoginModalActive" :width="420" scroll="keep"&gt;
    &lt;div class="card"&gt;
      &lt;div class="card-content"&gt;
        &lt;div class="media"&gt;
          &lt;div class="media-left"&gt;&lt;/div&gt;
          &lt;div class="media-content"&gt;
            &lt;p class="title is-4"&gt;新規登録/ログイン&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="content"&gt;
          &lt;button
            class="button sosial-button is-medium is-info is-fullwidth"
            style="background-color:white;color:red;border:2px solid; border-color:red;"
            @click="google"
          &gt;
            &lt;span class="icon"&gt;
              &lt;i class="fab fa-google"&gt;&lt;/i&gt;
              &amp;nbsp;
              &lt;span&gt;Google&lt;/span&gt;
            &lt;/span&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/b-modal&gt;
&lt;/template&gt;

&lt;script&gt;
import 'bulma/css/bulma.min.css'
import '@fortawesome/fontawesome-free/css/all.min.css'
import 'bulma-social/bin/bulma-social.min.css'

import firebase from 'firebase'
import { firestore, storage } from '~/plugins/firebase.js'

export default {
  data() {
    return {
      isLoginModalActive: false
    }
  },
  methods: {
    login() {
      this.isLoginModalActive = true
    },
    // Google認証を行う関数
    google() {
      // ② Google認証
      const auth = () =&gt; {
        return new Promise((resolve, reject) =&gt; {
          const authUI = new firebase.auth.GoogleAuthProvider()
          firebase
            .auth()
            .signInWithPopup(authUI)
            .then((result) =&gt; {
              resolve(result)
            })
            .catch((error) =&gt; {
              const errorCode = error.code
              const errorMessage = error.message
              const email = error.email
              const credential = error.credential
              reject(error)
            })
        })
      }

      // ③ 認証後のユーザー情報を取得してオブジェクト化
      const getAccountData = (result) =&gt; {
        return new Promise((resolve, reject) =&gt; {
          let userObject = {}
          let user = result.user
          userObject.token = result.credential.accessToken
          userObject.refreshToken = user.refreshToken
          userObject.uid = user.uid
          userObject.displayName = user.displayName
          userObject.photoURL = user.photoURL
          userObject.uid = user.uid
          userObject.email = user.email
          userObject.isNewUser = result.additionalUserInfo.isNewUser
          userObject.providerId = result.additionalUserInfo.providerId
          resolve(userObject)
        })
      }

      // 同期的に順番に処理を実行する
      Promise.resolve()
        .then(this.setPersistence)
        .then(auth)
        .then(getAccountData)
        .then((userObject) =&gt; this.createPhotoURL(userObject))
        .then((userObject) =&gt; this.setPublicUserData(userObject))
        .then((userObject) =&gt; this.setPrivateUserData(userObject))
        .then((userObject) =&gt; this.setLocalUserData(userObject))
        .catch((error) =&gt; this.onRejectted(error))
    },
    onRejectted(error) {
      this.$buefy.toast.open({
        duration: 5000,
        message: `ログインに失敗しました。`,
        position: 'is-bottom',
        type: 'is-danger'
      })
      this.isLoginModalActive = false
    },
    createPublicObj(obj) {
      let publicObj = {}
      publicObj.uid = obj.uid
      publicObj.providerId = obj.providerId
      publicObj.isNewUser = obj.isNewUser
      publicObj.photoURL = obj.photoURL
      publicObj.displayName = obj.displayName

      return publicObj
    },
    createPrivateObj(obj) {
      let privateObj = {}
      privateObj.uid = obj.uid
      privateObj.providerId = obj.providerId
      privateObj.isNewUser = obj.isNewUser
      privateObj.email = obj.email
      privateObj.token = obj.token
      privateObj.refreshToken = obj.refreshToken
      return privateObj
    },
    // ① 認証状態を明示的にセットする
    setPersistence() {
      return new Promise((resolve, reject) =&gt; {
        firebase
          .auth()
          .setPersistence(firebase.auth.Auth.Persistence.LOCAL)
          .then((result) =&gt; {
            resolve()
          })
      })
    },
    // ⑤ 公開可能なユーザー情報をFirestoreに登録
    setPublicUserData(userObject) {
      return new Promise((resolve, reject) =&gt; {
        let publicUser = firestore.collection('users').doc(userObject.uid)
        // ** usersに登録するObjのみを登録する
        publicUser
          .set(this.createPublicObj(userObject), { merge: true })
          .then((result) =&gt; {
            resolve(userObject)
          })
      })
    },
    // ⑥ 非公開のユーザー情報をFirestoreに登録
    setPrivateUserData(userObject) {
      return new Promise((resolve, reject) =&gt; {
        let privateUsers = firestore
          .collection('privateUsers')
          .doc(userObject.uid)
        // privateUsersに登録するObjのみを登録する
        privateUsers
          .set(this.createPrivateObj(userObject), { merge: true })
          .then((result) =&gt; {
            resolve(userObject)
          })
      })
    },
    // ⑦ ローカルストレージに保持するユーザー情報を設定
    setLocalUserData(userObject) {
      return new Promise((resolve, reject) =&gt; {
        let user = firestore.collection('users').doc(userObject.uid)
        user
          .get()
          .then((doc) =&gt; {
            if (doc.exists) {
              localStorage.setItem('photoURL', doc.data().photoURL)
              localStorage.setItem('uid', userObject.uid)
              localStorage.setItem('token', userObject.token)
              localStorage.setItem('displayName', doc.data().displayName)
              this.$buefy.toast.open({
                duration: 5000,
                message: `ログインに成功しました`,
                position: 'is-bottom',
                type: 'is-success'
              })
              this.isLoginModalActive = false
              location.reload()
              resolve(userObject)
            }
          })
          .catch((error) =&gt; {
            console.log('ログインに失敗しました。Error getting document:', error)
          })
      })
    },
    // ④ 取得したアイコンのURLをFirestorageに保存して、そのURLをFirestoreに登録する準備
    createPhotoURL(userObject) {
      return new Promise((resolve, reject) =&gt; {
        let url = userObject.photoURL
        let xhr = new XMLHttpRequest()
        xhr.responseType = 'blob'
        xhr.onload = function(event) {
          let blob = xhr.response
          let storageRef = storage.ref()
          let mountainsRef = storageRef.child(
            `user/${userObject.uid}/image.jpg`
          )
          let uploadTask = mountainsRef.put(blob)
          uploadTask.then((snapshot) =&gt; {
            uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) =&gt; {
              console.log(downloadURL)
              // firestorageに登録したURLを登録するオブジェクトに代入
              userObject.photoURL = downloadURL
              resolve(userObject)
            })
          })
        }
        // メッセージを受け取った後に返信する
        xhr.open('GET', url)
        xhr.send()
      })
    }
  }
}
&lt;/script&gt;
&lt;style&gt;
  .sosial-button {
    margin-top: 1rem;
  }
&lt;/style&gt;</pre><p>&nbsp;</p>
<h2>ユーザー認証の処理を順番に実行</h2>
<p>メソッドチェーンで順番に処理を実行していきます。</p>
<p>then内のいずれかでエラー(reject)が発生した場合にonRejecttedメソッドが呼び出されます。</p>
<p>Promise.resolve() メソッドは、引数の値でPromiseオブジェクトを返します。その値がプロミスであった場合は、そのプロミスが返されます。その値がthenable (すなわちthenメソッドを持っている場合) であれば、返されるプロミスは thenable を追跡し、その最終的な状態になります。</p>
<p>今回の場合は返り値がなくその後の処理もないため、①から⑦まで順番に実行して終了します。</p><pre class="crayon-plain-tag">// 順番に処理を実行する
Promise.resolve()
  .then(this.setPersistence) // ①
  .then(auth) // ②
  .then(getAccountData) // ③
  .then((userObject) =&gt; this.createPhotoURL(userObject)) // ④
  .then((userObject) =&gt; this.setPublicUserData(userObject)) // ⑤
  .then((userObject) =&gt; this.setPrivateUserData(userObject)) // ⑥
  .then((userObject) =&gt; this.setLocalUserData(userObject)) // ⑦
  .catch((error) =&gt; this.onRejectted(error))</pre><p>&nbsp;</p>
<h2>① 認証状態の永続性をセット</h2>
<p>setPersistenceメソッドで認証状態の永続性をセットします。</p>
<p>認証状態の永続性のタイプは「local」、「session」、「none」の３つがあります。詳しい説明などはこちらの公式サイトを参照ください。</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://firebase.google.com/docs/auth/web/auth-state-persistence?hl=ja">認証状態の永続性 &nbsp;|&nbsp; Firebase</a>
			</p>
										<div class="ys-blog-card__domain">firebase.google.com</div>
					</div>
	</div>
</div>

<p>認証状態の永続性は、firebase.auth().setPersistence メソッドを呼び出すことによって指定または変更できます。</p><pre class="crayon-plain-tag">// ① 認証状態の永続性をセットする
setPersistence() {
  return new Promise((resolve, reject) =&gt; {
    firebase
      .auth()
      .setPersistence(firebase.auth.Auth.Persistence.LOCAL)
      .then((result) =&gt; {
       resolve()
      })
  })
},</pre><p>&nbsp;</p>
<h2>② Google認証</h2>
<p>Googleの認証を行います。</p>
<p>firebase.auth.GoogleAuthProvider()メソッドでGoogle認証のプロバイドを呼び出します。それを引数に firebase.auth().signInWithPopup()メソッドで認証画面のポップアップを表示します。</p>
<p>Google認証完了後、認証の結果のプロミスを返します。エラーコードやエラーメッセージなどはcatch内の方法で取得できるため、Google認証に失敗した時のエラーハンドリングで使うことができます。</p><pre class="crayon-plain-tag">// ② Google認証
const auth = () =&gt; {
  return new Promise((resolve, reject) =&gt; {
    const authUI = new firebase.auth.GoogleAuthProvider()
    firebase
      .auth()
      .signInWithPopup(authUI)
      .then((result) =&gt; {
       resolve(result)
      })
      .catch((error) =&gt; {
        const errorCode = error.code
        const errorMessage = error.message
        const email = error.email
        const credential = error.credential
        reject(error)
      })
  })
}</pre><p>&nbsp;</p>
<h2>③ ユーザー情報を取得し、オブジェクト化</h2>
<p>Google認証の結果を元にユーザー情報を取得して、オブジェクト化します。オブジェクト化した結果のプロミスを返します。</p><pre class="crayon-plain-tag">// ③ 認証後のユーザー情報を取得してオブジェクト化
const getAccountData = (result) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    let userObject = {}
    let user = result.user
    userObject.token = result.credential.accessToken
    userObject.refreshToken = user.refreshToken
    userObject.uid = user.uid
    userObject.displayName = user.displayName
    userObject.photoURL = user.photoURL
    userObject.uid = user.uid
    userObject.email = user.email
    userObject.isNewUser = result.additionalUserInfo.isNewUser
    userObject.providerId = result.additionalUserInfo.providerId
    resolve(userObject)
  })
}</pre><p>&nbsp;</p>
<h2>④ アイコン画像をFirestorageに保存し、そのパスを取得</h2>
<p>GoogleのアイコンのURLからXMLHttpRequestでアイコン画像を取得します。</p>
<p>xhr.responseTypeでレスポンスの方をblobに定義しています。let blob = xhr.responseで取得した画像のblobオブジェクトをblobに渡しています。</p>
<p>その後、Storageのuser/${userObject.uid}/image.jpgパスを用意し、そこにアイコン画像データを保存します。</p>
<p>getDownloadURL()メソッドで、保存したアイコン画像のURLパスを取得し、userObject.photoURLに渡しています。</p>
<p>つまりGoogle認証時に取得したGoogle内に保持しているアイコン画像のパスからXMLHttpRequestでアイコン画像をblobタイプで取得し、それをFirestorageに保存し、保存したURLのパスを取得しFirestoreに保存するために再度userObject.photoURLにパスを渡しているといった処理になります。</p>
<p>xhr.send()の処理は、「Unchecked runtime.lastError: The message port closed before a response was received.」のエラーを回避するため、特に何もなくても返信しています。</p>
<p>参考：<a href="https://qiita.com/noenture/items/3978f638f2ffb8ff0995" target="_blank" rel="noopener noreferrer">https://qiita.com/noenture/items/3978f638f2ffb8ff0995</a></p><pre class="crayon-plain-tag">// ④ 取得したアイコンのURLをFirestorageに保存して、そのURLをFirestoreに登録する準備
createPhotoURL(userObject) {
  return new Promise((resolve, reject) =&gt; {
    let url = userObject.photoURL
    let xhr = new XMLHttpRequest()
    xhr.responseType = 'blob'
    xhr.onload = function(event) {
      let blob = xhr.response
      let storageRef = storage.ref()
      let mountainsRef = storageRef.child(
        `user/${userObject.uid}/image.jpg`
      )
      let uploadTask = mountainsRef.put(blob)
      uploadTask.then((snapshot) =&gt; {
        uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) =&gt; {
          // Firestorageに登録したURLを登録するオブジェクトに代入
          userObject.photoURL = downloadURL
          resolve(userObject)
        })
      })
    }
    // メッセージを受け取った後に返信する
    xhr.open('GET', url)
    xhr.send()
  })
}</pre><p>&nbsp;</p>
<h2>⑤ 公開可能なユーザー情報をFirestoreに登録</h2>
<p>ユーザー情報の中でも公開可能な情報をFirestoreに登録します。createPublicObjメソッドを呼び出しpublicObjに公開可能な情報をセットし、Firestoreのusersコレクション内にuidをkeyにして登録しています。</p>
<p>mergeオプションをつけるとフィールドの追加をしています。</p><pre class="crayon-plain-tag">// ⑤ 公開可能なユーザー情報をFirestoreに登録
setPublicUserData(userObject) {
  return new Promise((resolve, reject) =&gt; {
    let publicUser = firestore.collection('users').doc(userObject.uid)
    // usersに登録するObjのみを登録する
    publicUser
      .set(this.createPublicObj(userObject), { merge: true })
      .then((result) =&gt; {
        resolve(userObject)
      })
  })
},</pre><p></p><pre class="crayon-plain-tag">createPublicObj(obj) {
  let publicObj = {}
  publicObj.uid = obj.uid
  publicObj.providerId = obj.providerId
  publicObj.isNewUser = obj.isNewUser
  publicObj.photoURL = obj.photoURL
  publicObj.displayName = obj.displayName

  return publicObj
},</pre><p>&nbsp;</p>
<h2>⑥ 非公開なユーザー情報をFirestoreに登録</h2>
<p>公開可能のユーザ情報登録と同じように、非公開のユーザ情報も登録していきます。非公開情報ではメールやトークンが登録されています。</p><pre class="crayon-plain-tag">// ⑥ 非公開のユーザー情報をFirestoreに登録
setPrivateUserData(userObject) {
  return new Promise((resolve, reject) =&gt; {
    let privateUsers = firestore
      .collection('privateUsers')
      .doc(userObject.uid)
    // privateUsersに登録するObjのみを登録する
    privateUsers
      .set(this.createPrivateObj(userObject), { merge: true })
      .then((result) =&gt; {
        resolve(userObject)
      })
  })
},</pre><p></p><pre class="crayon-plain-tag">createPrivateObj(obj) {
  let privateObj = {}
  privateObj.uid = obj.uid
  privateObj.providerId = obj.providerId
  privateObj.isNewUser = obj.isNewUser
  privateObj.email = obj.email
  privateObj.token = obj.token
  privateObj.refreshToken = obj.refreshToken
  return privateObj
},</pre><p>&nbsp;</p>
<h2>⑦ ローカルストレージに保持するユーザー情報をセット</h2>
<p>Firestoreに保存した公開可能なユーザ情報から「アイコン画像パス」、「uid」、「トークン」、「表示名」を取得し、Local Storageにセットして、認証完了です。</p>
<p>ユーザーがログイン状態になり、更新情報を画面に反映させるためにリロードしています。</p><pre class="crayon-plain-tag">// ⑦ ローカルストレージに保持するユーザー情報を設定
setLocalUserData(userObject) {
  return new Promise((resolve, reject) =&gt; {
    let user = firestore.collection('users').doc(userObject.uid)
    user
      .get()
      .then((doc) =&gt; {
        if (doc.exists) {
          localStorage.setItem('photoURL', doc.data().photoURL)
          localStorage.setItem('uid', userObject.uid)
          localStorage.setItem('token', userObject.token)
          localStorage.setItem('displayName', doc.data().displayName)
          this.$buefy.toast.open({
            duration: 5000,
            message: `ログインに成功しました`,
            position: 'is-bottom',
            type: 'is-success'
          })
          this.isLoginModalActive = false
          location.reload()
          resolve(userObject)
        }
      })
      .catch((error) =&gt; {
        console.log(error)
    })
  })
},</pre><p>&nbsp;</p>
<h2>エラーハンドリング</h2>
<p>ユーザー認証のメソッドチェーン内でエラーが発生した場合は、catch内のonRejecttedメソッドが呼び出され、ログインに失敗するようハンドリングしています。</p><pre class="crayon-plain-tag">onRejectted(error) {
  this.$buefy.toast.open({
    duration: 5000,
    message: `ログインに失敗しました。`,
    position: 'is-bottom',
    type: 'is-danger'
  })
  this.isLoginModalActive = false
},</pre><p>&nbsp;</p>
<p>参考：<br />
<a href="https://github.com/FujiyamaYuta/nuxt-firebase-project" target="_blank" rel="noopener noreferrer">https://github.com/FujiyamaYuta/nuxt-firebase-project</a><br />
<a href="https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest" target="_blank" rel="noopener noreferrer">https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest</a><br />
<a href="https://qiita.com/ueokande/items/807a6c9a64c3874a0f83" target="_blank" rel="noopener noreferrer">https://qiita.com/ueokande/items/807a6c9a64c3874a0f83</a><br />
<a href="https://qiita.com/toshihirock/items/e49b66f8685a8510bd76" target="_blank" rel="noopener noreferrer">https://qiita.com/toshihirock/items/e49b66f8685a8510bd76</a><br />
<a href="https://qiita.com/noenture/items/3978f638f2ffb8ff0995" target="_blank" rel="noopener noreferrer">https://qiita.com/noenture/items/3978f638f2ffb8ff0995</a></p>The post <a href="https://mintaku-blog.net/firebase-google/">Firebase + Nuxt.jsを使ったGoogleアカウント認証を実装する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/firebase-google/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1728</post-id>	</item>
		<item>
		<title>Firebase HostingからCloud Runに独自ドメインを移管する</title>
		<link>https://mintaku-blog.net/run-domain/</link>
					<comments>https://mintaku-blog.net/run-domain/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Thu, 29 Oct 2020 08:52:47 +0000</pubDate>
				<category><![CDATA[GCP]]></category>
		<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1691</guid>

					<description><![CDATA[<p>Firebase Hostingに紐づいた独自ドメインをからCloud Runに移管する方法を紹介します。 Cloud Runに紐づけるドメインのリソースを …</p>
The post <a href="https://mintaku-blog.net/run-domain/">Firebase HostingからCloud Runに独自ドメインを移管する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Firebase Hostingに紐づいた独自ドメインをからCloud Runに移管する方法を紹介します。</p>
<h2>Cloud Runに紐づけるドメインのリソースをデプロイ</h2>
<p><img data-attachment-id="1693" data-permalink="https://mintaku-blog.net/run-domain/image4/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?fit=1999%2C356&amp;ssl=1" data-orig-size="1999,356" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image4" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?fit=300%2C53&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?fit=800%2C142&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1693" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?resize=800%2C142&#038;ssl=1" alt="" width="800" height="142" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?resize=1024%2C182&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?resize=300%2C53&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?resize=768%2C137&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?resize=1536%2C274&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?w=1999&amp;ssl=1 1999w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image4.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>事前に独自ドメインに紐づけるリソースをCloud Runにデプロイしておきます。デプロイすると上の画像のようにサービスが追加されていることが確認できます。</p>
<h2>マッピングの追加</h2>
<p><img data-attachment-id="1695" data-permalink="https://mintaku-blog.net/run-domain/image2-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?fit=1084%2C844&amp;ssl=1" data-orig-size="1084,844" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?fit=300%2C234&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?fit=800%2C623&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1695" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?resize=800%2C623&#038;ssl=1" alt="" width="800" height="623" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?resize=1024%2C797&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?resize=300%2C234&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?resize=768%2C598&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-1.png?w=1084&amp;ssl=1 1084w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>Cloud Runのカスタムドメインを管理からマッピングの追加を行います。</p>
<p>Cloud Runにデプロイした独自ドメインと紐付けたいリソースを選択し、紐づける独自ドメインを入力します。</p>
<p>ドメインの確認ができていない場合は、ウェブマスターセントラルに進んで確認を完了させます。</p>
<p>&nbsp;</p>
<h2>DNSレコードの更新</h2>
<p><img data-attachment-id="1692" data-permalink="https://mintaku-blog.net/run-domain/image5/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?fit=1114%2C1412&amp;ssl=1" data-orig-size="1114,1412" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image5" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?fit=237%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?fit=800%2C1014&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1692" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?resize=800%2C1014&#038;ssl=1" alt="" width="800" height="1014" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?resize=808%2C1024&amp;ssl=1 808w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?resize=237%2C300&amp;ssl=1 237w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?resize=768%2C973&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image5.png?w=1114&amp;ssl=1 1114w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>所有権の証明が完了したら、DNSレコードを更新します。表示されたDNSレコードをCloud DNSに登録します。</p>
<p>今回は既に設定する独自ドメインがFirebase Hostingに紐づいているため、そのDNSレコードを削除して登録します。このタイミングでFirebase Hostingされている既存のサイトが見れなくなるので注意が必要です。</p>
<p>大体15分ほど経つと、Cloud Runのリソースに設定した独自ドメインが紐づきます。</p>
<p>&nbsp;</p>
<p><img data-attachment-id="1696" data-permalink="https://mintaku-blog.net/run-domain/image1-4/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?fit=804%2C418&amp;ssl=1" data-orig-size="804,418" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?fit=300%2C156&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?fit=800%2C416&amp;ssl=1" loading="lazy" class="aligncenter size-full wp-image-1696" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?resize=800%2C416&#038;ssl=1" alt="" width="800" height="416" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?w=804&amp;ssl=1 804w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?resize=300%2C156&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1-1.png?resize=768%2C399&amp;ssl=1 768w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>上の画像のようにCloud Runのサービス詳細画面のURLにあるカスタムドメインの欄に紐付けたいドメインが表示されていれば完了です。</p>
<p>&nbsp;</p>
<h2>Firebase Hostingの画面で独自ドメインの設定が解除されたか確認</h2>
<p><img data-attachment-id="1694" data-permalink="https://mintaku-blog.net/run-domain/image3-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?fit=1998%2C850&amp;ssl=1" data-orig-size="1998,850" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image3" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?fit=300%2C128&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?fit=800%2C341&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1694" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?resize=800%2C341&#038;ssl=1" alt="" width="800" height="341" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?resize=1024%2C436&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?resize=300%2C128&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?resize=768%2C327&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?resize=1536%2C653&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?w=1998&amp;ssl=1 1998w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3-1.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>Firebase Hostingの画面にアクセスすると、ドメインの紐付けが解除されており、「設定が必要です」となっているため、後は必要に応じてドメインを削除しましょう。</p>The post <a href="https://mintaku-blog.net/run-domain/">Firebase HostingからCloud Runに独自ドメインを移管する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/run-domain/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1691</post-id>	</item>
		<item>
		<title>【Nuxt.js】Firebase Hosting + Cloud RunでSSRする方法</title>
		<link>https://mintaku-blog.net/nuxt-run/</link>
					<comments>https://mintaku-blog.net/nuxt-run/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Mon, 19 Oct 2020 04:45:55 +0000</pubDate>
				<category><![CDATA[GCP]]></category>
		<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[Firebase]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1679</guid>

					<description><![CDATA[<p>Firebase HostingでホスティングしたNuxt.jsプロジェクトを一部Cloud RunでSSRする方法を紹介します。なおFirebase Ho …</p>
The post <a href="https://mintaku-blog.net/nuxt-run/">【Nuxt.js】Firebase Hosting + Cloud RunでSSRする方法</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Firebase HostingでホスティングしたNuxt.jsプロジェクトを一部Cloud RunでSSRする方法を紹介します。なおFirebase HostingとCloud Runは同じプロジェクト内で作成する必要があります。</p>
<h2>Nuxt.jsのアプリをコンテナ化し、Container Registryに登録し、Cloud Runにデプロイする方法</h2>
<h3>Cloud BuildとCloud Runの有効化</h3>
<p>まずはCloud Build APIとCloud Run APIを有効化しておきます。</p><pre class="crayon-plain-tag"># Cloud Buildの有効化
$ gcloud services enable cloudbuild.googleapis.com</pre><p></p><pre class="crayon-plain-tag"># Cloud Runの有効化
$ gcloud services enable run.googleapis.com</pre><p>&nbsp;</p>
<h3>nuxt.config.jsの変更</h3>
<p>nuxt.config.jsにserverオプションを追加します。</p>
<p>hostはlocalhostや127.0.0.1ではなく、0.0.0.0に設定する必要があります。</p>
<p>&nbsp;</p>
<p>・nuxt.config.js</p><pre class="crayon-plain-tag">export default {
  server: {
    port: 3000, // default: 3000
    host: '0.0.0.0', // default: localhost,
    timing: false
  }
}</pre><p>&nbsp;</p>
<h3>cloudbuild.yamlを作成する</h3>
<p>cloudbuild.yamlの書き方は以下の公式サイトを参考にしています。</p>
<p>コンテナイメージをビルドして、Container Registryにプッシュしています。その後、Container RegistryからCloud Runにデプロイしています。</p>
<p>参考：</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/docs.cloud.google.com/_static/cloud/images/social-icon-google-cloud-1200-630.png?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://cloud.google.com/cloud-build/docs/deploying-builds/deploy-cloud-run?hl=ja">Cloud Build を使用した Cloud Run へのデプロイ &nbsp;|&nbsp; Google Cloud Documentation</a>
			</p>
										<div class="ys-blog-card__domain">cloud.google.com</div>
					</div>
	</div>
</div>

<p>&nbsp;</p>
<p>・cloudbuild.yaml</p><pre class="crayon-plain-tag">steps:
  # Build the container image
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/test-staging/nuxt-ssr', '.']
  # Push the container image to Container Registry
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/test-staging/nuxt-ssr']
  # Deploy container image to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args: ['run', 'deploy', 'nuxt-ssr-test', '--image', 'gcr.io/test-staging/nuxt-ssr', '--region', 'asia-northeast1', '--platform', 'managed', '--port', '3000']
images:
  - gcr.io/test-staging/nuxt-ssr</pre><p>&nbsp;</p>
<h3>Dockerfileを作成する</h3>
<p>次にNuxt.jsのアプリをコンテナ化するためにDockerfileを作成します。</p>
<p>参考：</p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/v2.nuxt.com/preview.png?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://ja.nuxtjs.org/faq/deployment-cloud-run/">Nuxt 2 - Google Cloud Run</a>
			</p>
							<div class="ys-blog-card__dscr">
					Nuxt を Google Cloud Run にどうやってデプロイするのか？				</div>
										<div class="ys-blog-card__domain">ja.nuxtjs.org</div>
					</div>
	</div>
</div>

<p>&nbsp;</p>
<p>・Dockerfile</p><pre class="crayon-plain-tag">FROM node:14

# https://docs.docker.jp/engine/reference/builder.html#workdir
WORKDIR /usr/src/app

COPY package.json ./
RUN npm install

COPY . .

# https://docs.docker.jp/engine/reference/builder.html#expose
# EXPOSE 命令はコンテナの実行時に、所定ネットワーク上のどのポートをリッスンするかを指定
EXPOSE 3000

RUN npm run build

CMD [ "npm", "run", "start" ]</pre><p>&nbsp;</p>
<p>gcloud initした後に以下のコマンドでcloudbuild.yamlのコマンドが走ります。</p><pre class="crayon-plain-tag">$ gcloud builds submit</pre><p>&nbsp;</p>
<p>成功すると、Container Registryにコンテナイメージが登録され、Cloud Runにコンテナがデプロイされていることが確認できます。</p>
<p>・Container Registry</p>
<p><img data-attachment-id="1682" data-permalink="https://mintaku-blog.net/nuxt-run/image2-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?fit=1999%2C104&amp;ssl=1" data-orig-size="1999,104" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?fit=300%2C16&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?fit=800%2C41&amp;ssl=1" loading="lazy" class="aligncenter wp-image-1682 size-large" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173-1024x53.png?resize=800%2C41&#038;ssl=1" alt="" width="800" height="41" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?resize=1024%2C53&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?resize=300%2C16&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?resize=768%2C40&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?resize=1536%2C80&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?w=1999&amp;ssl=1 1999w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image2-e1602640857173.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<p>・Cloud Run</p>
<p><img data-attachment-id="1683" data-permalink="https://mintaku-blog.net/nuxt-run/image1-3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?fit=1796%2C96&amp;ssl=1" data-orig-size="1796,96" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?fit=300%2C16&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?fit=800%2C43&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1683" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?resize=800%2C43&#038;ssl=1" alt="" width="800" height="43" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?resize=1024%2C55&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?resize=300%2C16&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?resize=768%2C41&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?resize=1536%2C82&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?w=1796&amp;ssl=1 1796w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image1.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<h3>Cloud Runにデプロイしたリソースにメンバーとロールの追加</h3>
<p><img data-attachment-id="1681" data-permalink="https://mintaku-blog.net/nuxt-run/image3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?fit=1384%2C898&amp;ssl=1" data-orig-size="1384,898" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image3" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?fit=300%2C195&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?fit=800%2C519&amp;ssl=1" loading="lazy" class="aligncenter size-large wp-image-1681" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?resize=800%2C519&#038;ssl=1" alt="" width="800" height="519" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?resize=1024%2C664&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?resize=300%2C195&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?resize=768%2C498&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/10/image3.png?w=1384&amp;ssl=1 1384w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>Cloud Runにデプロイしたままでは、デプロイしたアプリは誰もアクセスすることができません。</p>
<p>アクセスできるようにするために、Cloud Runにデプロイしたリソースにメンバーとロールを追加します。</p>
<p>ここでは全てのユーザが見れるようにメンバーを「allUsers」にし、ロールを「Cloud Run起動元」で追加します。「allUsers」すると、デプロイしたリソースは一般公開され、インターネット上から誰でもアクセスできる状態になるため、注意していください。</p>
<p>保存後、Cloud Runに記載されているURLにアクセすることができるようになります。</p>
<p>&nbsp;</p>
<h2>Firebase HostingからCloud Runにリクエストをリダイレクトする</h2>
<p>Firebase HostingでホスティングしたNuxt.jsプロジェクトを一部Cloud RunでSSRします。Cloud RunでSSRしたいリソースをfirebase.jsonのrewritesに追加します。</p>
<p>今回の場合は、/testsをCloud Runのリソースを使うようにします。つまり、/testsへのアクセスがあった場合にFirebase Hostingのリソースを使わずに、Cloud Runのリソースを使うようリクエストをリダイレクトします。</p>
<p>なおFirebase Hostingの/testsのリソースは空にしておかないと、正常にリダイレクトされないので注意しましょう。</p>
<p>serviceIdは先ほどデプロイしたCloud Runのサービス、regionはCloud Runで使用しているリージョンを選択します。後はfirebase.jsonをデプロイして完了です。</p>
<p>・firebase.json</p><pre class="crayon-plain-tag">...

  "rewrites": [
  {
    "source": "/tests",
    "run": {
      "serviceId": "nuxt-ssr-test",
      "region": "asia-northeast1"
    }
  }
],

...</pre><p>&nbsp;</p>The post <a href="https://mintaku-blog.net/nuxt-run/">【Nuxt.js】Firebase Hosting + Cloud RunでSSRする方法</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/nuxt-run/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1679</post-id>	</item>
		<item>
		<title>「Jamstack」アーキテクチャによるWeb開発【Nuxt.js×MicroCMS×Firebase】</title>
		<link>https://mintaku-blog.net/jamstack/</link>
					<comments>https://mintaku-blog.net/jamstack/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Fri, 03 Jul 2020 14:01:26 +0000</pubDate>
				<category><![CDATA[GCP]]></category>
		<category><![CDATA[Nuxt.js]]></category>
		<category><![CDATA[Firebase]]></category>
		<category><![CDATA[アーキテクチャ]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=1537</guid>

					<description><![CDATA[<p>新規開発で「Jamstack」アーキテクチャを取り入れて開発したので、その過程やメリットなどについてまとめておきます。 Jamstack構成にあたり以下のサ …</p>
The post <a href="https://mintaku-blog.net/jamstack/">「Jamstack」アーキテクチャによるWeb開発【Nuxt.js×MicroCMS×Firebase】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>新規開発で「Jamstack」アーキテクチャを取り入れて開発したので、その過程やメリットなどについてまとめておきます。</p>
<p>Jamstack構成にあたり以下のサービスを活用しています。</p>
<ul>
<li>静的サイトジェネレータ：Nuxt.js</li>
<li>ヘッドレスCMS：microCMS</li>
<li>ホスティングサービス：Firebase Hosting</li>
</ul>
<h2>Jamstackとは</h2>
<p>Jamstack(ジャムスタック)とはウェブサイトを実現するためのJAMの頭文字からなる技術の組み合わせです。JAMはそれぞれJavaScript・API・ Markupを表しています。</p>
<p>Jamstackは、サーバとフロントを疎結合にし、サーバが担っていた機能を外部化してフロントだけで扱いやすくする構成となっています。</p>
<p>公式サイト：<a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">https://jamstack.org/</a></p>
<h2>Jamstackの特徴</h2>
<ul>
<li>Webサイトの各ページのHTMLファイルは事前に静的サイトジェネレータで生成</li>
<li>ブラウザからアクセス時にCDNから配信</li>
<li>Webサーバに依存しないサーバレスな構成</li>
</ul>
<p>Jamstackは以上のような特徴が挙げられます。</p>
<p>閲覧専用でユーザごとにコンテンツの出し分けがないようなWebサービスに最も適していると考えられます。パッと思いつくのは、ユーザ認証等がないWebメディアやコーポレートサイトで効果を発揮しそうです。</p>
<h2>Jamstackで使われるサービス</h2>
<p>このようなJamstack構成を実現させるために、以下のようなサービスが使われます。</p>
<ul>
<li>静的サイトジェネレータ</li>
<li>ヘッドレスCMS</li>
<li>ホスティングサービス</li>
</ul>
<p>Jamstackアーキテクチャの採用にあたり今回開発した新規サービスでは、静的サイトジェネレータはNuxt.js、ヘッドレスCMSはmicroCMS、ホスティングサービスはFirebase Hostingを採用しています。</p>
<p>ホスティングサービスは他にNetlifyが有名(そもそもNetlifyのCEOがJamstack提唱したっぽい)で、そちらはWebhookによるトリガが可能(Firebase Hostingではできないっぽい？)なので、ヘッドレスCMSから記事投稿時にWebhookによる通知をして、自動ビルドできます。</p>
<p>Firebase Hostingでは調べた限りでは、Webhookによるトリガができないようなので、新規サービスではGitプッシュ時とスケジューリングで1日1回自動ビルドするように運用しています。どうしてもその日に公開したい記事の場合は手動でビルド・デプロイといった感じです。</p>
<h2>Jamstack導入のメリット</h2>
<p>Jamstackには以下のようメリットがあります。</p>
<p>・高パフォーマンス<br />
→nuxt generateによってデプロイ前にあらかじめ静的ページを生成することでCDN配信が可能となり、より高いパフォーマンスを実現できました。</p>
<p>・高セキュリティ<br />
→コンテンツ等はあらかじめ静的ファイルとしてホスティングしているため、サーバ側のプロセスが必要最低限のAPIに集約され、脆弱性に対する攻撃対象の減少が期待できます。</p>
<p>・コスト削減<br />
→主にコンテンツ配信の手段として利用するため、コストの削減が可能となります。</p>
<p>・SEO向上<br />
→SSGによるページ遷移の高速化・サーバ側でレンダリングしたHTMLを読み込むことにより、SEO向上が期待できます。</p>
<p>また、コンテンツにおけるAPIレスポンスは、ビルドのタイミングで全部取得してホスティングしているため、安定的な稼働が可能となります。</p>
<p>逆に言えば、ビルド時にAPIがおかしかったらエラーがでるため、デプロイ前に気づくことができるのも良い点かと思います。</p>
<h2>Jamstack採用において</h2>
<p>今回の新規サービスでは、認証機能やユーザ登録・更新処理などが追加実装されたため、完全なJamstack構成というより、部分的Jamstack構成になりました。</p>
<p>もし、認証機能やユーザ登録・更新処理などがなくコンテンツのみの場合は以下のような構成になります。</p>
<p><img data-attachment-id="1555" data-permalink="https://mintaku-blog.net/jamstack/%e6%96%b0%e8%a6%8f%e3%82%b5%e3%83%bc%e3%83%92%e3%82%99%e3%82%b9%e3%81%ab%e3%81%8a%e3%81%91%e3%82%8bjamstack%e6%a7%8b%e6%88%90%e3%82%b3%e3%83%b3%e3%83%86%e3%83%b3%e3%83%84%e3%81%ae%e3%81%bf%e3%81%ae-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?fit=960%2C540&amp;ssl=1" data-orig-size="960,540" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="新規サービスにおけるJamstack構成(コンテンツのみの場合)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-full wp-image-1555" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?w=960&amp;ssl=1 960w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/82b91a00e4e1849240ed56b95f23673e.png?resize=768%2C432&amp;ssl=1 768w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>これが静的ファイルのみで構成される一般的なJamstack構成になると思います。Firebase HostingからCDN配信で完結しているため、サイトの高速化・高セキュリティ・サーバレスとなります。</p>
<p>なんとかヘッドレスCMSからの記事投稿をトリガに自動ビルド・デプロイできれば理想型って感じですかね。</p>
<p>&nbsp;</p>
<p>実際の新規サービスでは認証機能等あるので、以下のような構成になります。</p>
<p><img data-attachment-id="1553" data-permalink="https://mintaku-blog.net/jamstack/gcp%e5%9b%b3%e8%a7%a3/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?fit=960%2C540&amp;ssl=1" data-orig-size="960,540" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="GCP図解" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-full wp-image-1553" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?w=960&amp;ssl=1 960w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/08752a3329319d2e60dfc60c4cdd5b51.png?resize=768%2C432&amp;ssl=1 768w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p><img data-attachment-id="1554" data-permalink="https://mintaku-blog.net/jamstack/%e6%96%b0%e8%a6%8f%e3%82%b5%e3%83%bc%e3%83%92%e3%82%99%e3%82%b9%e3%81%ab%e3%81%8a%e3%81%91%e3%82%8bjamstack%e6%a7%8b%e6%88%90%e2%91%a1-2/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?fit=960%2C540&amp;ssl=1" data-orig-size="960,540" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="新規サービスにおけるJamstack構成②" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?fit=800%2C450&amp;ssl=1" loading="lazy" class="aligncenter size-full wp-image-1554" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?resize=800%2C450&#038;ssl=1" alt="" width="800" height="450" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?w=960&amp;ssl=1 960w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?resize=300%2C169&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2020/06/98f711aee4d84aeb00b97dbf0e5335a5.png?resize=768%2C432&amp;ssl=1 768w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<ul>
<li>コンテンツはあらかじめビルドしてある静的ファイルをFirebase HostingからCDN配信</li>
<li>認証はFirebase Authenticationを利用</li>
<li>ユーザ登録やユーザ情報更新等は別途APIからやり取り</li>
</ul>
<p>コンテンツの表示スピードの高速化やSEO向上、ユーザ情報登録・更新や検索のみサーバを使っているのでコストの削減もでき、Jamstackの恩恵を十分に受けられていると感じています。</p>
<h2>まとめ</h2>
<p>実際にJamstackアーキテクチャで新規サービスを開発してみて、サイトスピードが高速化し安定的に稼働していていい感じです。</p>
<p>毎回ビルドするといった手間はありますが、CI/CDツールを取り入れればそんな手間ではなくなります。</p>
<p>静的化できるファイルが多くある場合は、Jamstackアーキテクチャを取り入れるのが良さそうですね。</p>
<div id="gtx-trans" style="position: absolute; left: 38px; top: 2037.06px;">
<div class="gtx-trans-icon"></div>
</div>The post <a href="https://mintaku-blog.net/jamstack/">「Jamstack」アーキテクチャによるWeb開発【Nuxt.js×MicroCMS×Firebase】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/jamstack/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1537</post-id>	</item>
	</channel>
</rss>
