おはようございます。そうすけです!
運営者:そうすけ
愛媛出身の30代のブロガー兼ソフトウェアエンジニア。
主にフロント・バックの開発を行っています。
趣味はガジェットと植物。
本記事ではNetlify・FIREBASEにnuxt3を使ってGoogleMapApiを使用して本番デプロイしたときのエラーをまとめています。
個人開発でに数日つまづいたので、記載しておきます。
アプリ開発ポートフォリオ作成でGoogle関係のAPIを使う方参考になれば幸いです。
ローカル環境では起動しますが、FIREBASE・Netlifyの本番環境でも起こりますので、本番共通のエラーのようです。
今回はNuxtプロジェクトを簡単にデプロイできるNetlifyを使用しました。
デプロイしたコード
詳細検索すればサウナ一覧がでるサウナアプリです。
ざっくりいうと、NUXT3のcomporsablesディレクトリでインスタンス化したloaderを、コンポーネント側で実行して検索した値を取得しています。
<template>
<div class="container mx-auto w-full">
<div class="grid 2xl:grid-cols-6 xl:grid-cols-5 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-3 grid-cols-2 gap-x-3 gap-y-6 ">
<div v-for="(spa,index) in result">
<NuxtLink :to="`/map/${spa.place_id}`">
<div class="border-2 border-orange-200 w-50 sm:w-52 rounded-md shadow-lg">
<div>
<img :src="spa.photos[0].getUrl()"
class="h-44 w-full object-cover rounded-t-md">
</div>
<div class="flex flex-col items-center py-1">
<div class="w-40 px-2 overflow-hidden whitespace-nowrap text-ellipsis">
{{ spa.name }}
</div>
<div>
</div>
</div>
</div>
</NuxtLink>
</div>
</div>
</div>
<script lang="ts" setup>
//googlemap API関数
//インスタンスを取得
const loader = useGoogle()
const getData = ():void =>{
//検索結果宣言
const searchResult = ref([])
//検索実行関数
loader.load().then((google) => {
const geocoder = new google.maps.Geocoder()
//位置情報のインスタンス化
let latLng: google.maps.LatLng = new google.maps.LatLng(0.0)
geocoder.geocode({
//入力した値をリクエスト
//クエリパラメータにある指定都市の名称をリクエスト
address:searchPoint.value
},
//callback関数
//結果オブジェクトとステータスオブジェクトが帰ってくる
(result:any,status) => {
//レスポンスとステータスを引数に取れる
if(status === google.maps.GeocoderStatus.OK){
//周辺検索用に緯度と経度を取得する
const center = result[0]
latLng = center.geometry.location
}
}
)
//html要素を持たせたマッププレイスサービスを定義
const map = new google.maps.Map(document.createElement('div'))
const service = new google.maps.places.PlacesService(map)
//テキスト検索用のリクエスト
service.textSearch(
{
//緯度経度
location:latLng,
//検索する半径
radius:2000,
//検索ワード
query:
'サウナ' + ' ' + searchPoint.value + ' ' + searchCondition.value,
//レスポンス言語
language:'ja'
},
//第二引数で処理
//callback関数
//結果オブジェクトとステータスオブジェクトが帰ってくる
(result,status) =>{
//レスポンスとステータスを引数にとれる
if(
status === google.maps.places.PlacesServiceStatus.OK ||
status === google.maps.places.PlacesServiceStatus.ZERO_RESULTS
){
result!.forEach((element) => {
const format =element.formatted_address!.slice(12)
element.formatted_address = format
})
searchResult.value =result!
emits('getdatas',searchResult.value)
}
}
)
})
.catch(
() =>{
})
//検索値を表示
searchPoint_w.value=searchPoint.value
searchCondition_w.value=searchCondition.value
//textを空に
// searchPoint.value=""
// searchCondition.value=""
}
</script>
import {Loader} from '@googlemaps/js-api-loader'
export const useGoogle = () => {
//const runtimeConfig = useRuntimeConfig();
const loader = new Loader({
apiKey:'取得したAPIキー',
version:'weekly',
libraries:['places','drawing','geometry']
})
return (loader)
}
この方をベースに開発してます。開発方法は本筋からそれるので、こちらを覗いてみて下さい。
Netlifyで発生した3つのエラー
Netlifyに沿って本番デプロイ後のエラーです。
デプロイ方法は別途記事にします。
404エラーが発生する
これはNuxt3共通に起こる事象です。
Netlifyのデフォルトコンパイル先(publish directory)のファイルを.nuxt/distではなくdistに変更します。
デフォルトだと.nuxt/distになっていていますので変更してください。
あるあるエラーのようです。
GoogleAPIのエラー:CommonJS
エミュレーターや本番環境で以下のエラーがでます。
500
Named export 'Loader' not found. The requested module '@googlemaps/js-api-loader' is a CommonJS module, which may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, for example using: import pkg from '@googlemaps/js-api-loader'; const { Loader } = pkg;
解決方法
言われたとおりに変更します。
//import {Loader} from '@googlemaps/js-api-loader'
//netlifyのエラーにつき、記述方法を変更
import * as pkg from '@googlemaps/js-api-loader';
const { Loader } = pkg;
export const useGoogle = () => {
//const runtimeConfig = useRuntimeConfig();
const loader = new Loader({
apiKey:'APIキー',
version:'weekly',
libraries:['places','drawing','geometry']
})
return (loader)
}
loaderのインスタンス化のタイミング
起動時に変数宣言してインスタンス化するようになっている、次にこのエラーがでてきます。
500 Loader is not a constructor
外部APIなのにコンストラクタとして起動時に実行されると、レンダリング(描画)時に起動するなとエラーが起こるようです。
解決方法
- Onmountedを使用する
- 使用する関数内でインスタンス化する
<template>
<div class="container mx-auto w-full">
<!-- <div class="flex flex-wrap justify-start gap-4"> -->
<div class="grid 2xl:grid-cols-6 xl:grid-cols-5 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-3 grid-cols-2 gap-x-3 gap-y-6 ">
<div v-for="(spa,index) in result">
<NuxtLink :to="`/map/${spa.place_id}`">
<div class="border-2 border-orange-200 w-50 sm:w-52 rounded-md shadow-lg">
<div>
<img :src="spa.photos[0].getUrl()"
class="h-44 w-full object-cover rounded-t-md">
</div>
<div class="flex flex-col items-center py-1">
<div class="w-40 px-2 overflow-hidden whitespace-nowrap text-ellipsis">
{{ spa.name }}
</div>
<div>
</div>
</div>
</div>
</NuxtLink>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
//googlemap API関数
const getData = ():void =>{
//インスタンスを取得
const loader = useGoogle()
//検索結果宣言
const searchResult = ref([])
//検索実行関数
loader.load().then((google) => {
const geocoder = new google.maps.Geocoder()
//位置情報のインスタンス化
let latLng: google.maps.LatLng = new google.maps.LatLng(0.0)
geocoder.geocode({
//入力した値をリクエスト
//クエリパラメータにある指定都市の名称をリクエスト
address:searchPoint.value
},
//callback関数
//結果オブジェクトとステータスオブジェクトが帰ってくる
(result:any,status) => {
//レスポンスとステータスを引数に取れる
if(status === google.maps.GeocoderStatus.OK){
//周辺検索用に緯度と経度を取得する
const center = result[0]
latLng = center.geometry.location
}
}
)
//html要素を持たせたマッププレイスサービスを定義
const map = new google.maps.Map(document.createElement('div'))
const service = new google.maps.places.PlacesService(map)
//テキスト検索用のリクエスト
service.textSearch(
{
//緯度経度
location:latLng,
//検索する半径
radius:2000,
//検索ワード
query:
'サウナ' + ' ' + searchPoint.value + ' ' + searchCondition.value,
//レスポンス言語
language:'ja'
},
//第二引数で処理
//callback関数
//結果オブジェクトとステータスオブジェクトが帰ってくる
(result,status) =>{
//レスポンスとステータスを引数にとれる
if(
status === google.maps.places.PlacesServiceStatus.OK ||
status === google.maps.places.PlacesServiceStatus.ZERO_RESULTS
){
result!.forEach((element) => {
const format =element.formatted_address!.slice(12)
element.formatted_address = format
})
searchResult.value =result!
emits('getdatas',searchResult.value)
}
}
)
})
.catch(
() =>{
})
//検索値を表示
searchPoint_w.value=searchPoint.value
searchCondition_w.value=searchCondition.value
//textを空に
// searchPoint.value=""
// searchCondition.value=""
}
</script>
const loader = useGoogle()をget関数内に入れました。
インスタンスを取得する部分を関数内にいれることで、マウント時(templateがHTMLにコンパイルされるとき)にインスタンス化されないようにしました。
あとページ読み込み時にGoogleMapを表示したい場合はすべてにOnmountedをつけましょう。
<template>
<div>
<div class="container mx-auto mb-10">
<swiper-container
navigation="true"
pagination="true"
scrollbar="true"
>
<swiper-slide
v-for="(photo, i) in spa.photos" :key="i" eager>
<div>
<a :href="spa.url" target="_blank"
><img class="object-cover h-80 lg:h-96 mx-auto w-full xl:max-w-screen-lg" :src="photo.getUrl()" /></a>
</div>
</swiper-slide>
</swiper-container>
<div class= "flex justify-center my-4" >
<tbody class="border rounded-md border-gray-300 grid grid-cols-2 divide-y-2 divide-gray-300 [&>td]:p-2">
<th class="border-t-2 border-gray-300">項目</th>
<th>詳細</th>
<td>店名</td>
<td>{{ spa.name }}</td>
<td>評価</td>
<td>{{ spa.rating }}</td>
<td>住所</td>
<td>{{ spa.vicinity }}</td>
<td>営業ステータス</td>
<!-- //営業中か判定する -->
<td v-if="spa.opening_hours?.isOpen()" class="text-green-600">
〇営業中
</td>
<td v-else class="text-red-500">
✖営業時間外
</td>
<td>営業時間</td>
<td>
<div v-for="(opentime,index) in spa.opening_hours?.weekday_text"
:key="opentime">
{{ opentime }}
</div>
</td>
<td>WEbサイト</td>
<td><a :href="spa.website" target="_blank">{{ spa.website }}</a></td>
</tbody>
</div>
</div>
<swiper-container
navigation="true"
pagination="true"
scrollbar="true"
>
<!-- <div class="flex justify-center "> -->
<swiper-slider v-for="(review) in spa.reviews" class="mx-4 ">
<div class="review_card rounded-md">
<img :src="review.profile_photo_url" class="review_card_pic">
<p>{{ review.author_name }}</p>
<p>{{ review.text }}</p>
</div>
</swiper-slider>
</swiper-container>
<h2 class="bg-slate-400">周辺マップ</h2>
<div id='map' class="map_size">
google map
</div>
</div>
</template>
<script lang="ts" setup>
//スライダー実装
import { register } from 'swiper/element/bundle';
register();
//ルートIDをURLより取得
const route = useRoute()
const queryPlaceID =route.params.id
const{reviewer,setDialogData} = useDialog()
//spaで空のオブジェクトを宣言
const spa = ref<google.maps.places.PlaceResult>({})
console.log(spa.value)
onMounted(()=>{
const loader = useGoogle()
loader
.load()
.then((google)=>{
//mapインスタンス作成 初期描画用
const map = new google.maps.Map(document.getElementById('map') ,{
//初期表示設定
//適当な値で表示
zoom:17,
center: new google.maps.LatLng(0,0),
fullscreenControl:false,
mapTypeControl:false,
streetViewControl:true,
streetViewControlOptions:{
position:google.maps.ControlPosition.LEFT_BOTTOM
},
scaleControl:true
})
const service = new google.maps.places.PlacesService(map)
//受け取ったパラメータを元にmap情報の詳細を検索
//getDetails(A,B)
//Aでリクエスト送る
//Bで戻ってきた関数の実行
service.getDetails({
placeId:queryPlaceID
},
(place,status)=>{
if(status === google.maps.places.PlacesServiceStatus.OK){
spa.value = place
useHead({title: spa.value.name})
//位置情報を取得
map.setCenter(
new google.maps.LatLng(
place.geometry.location.lat(),
place.geometry.location.lng()
)
)
//マーカーオブジェクトをつける
new google.maps.Marker({
map,
position: new google.maps.LatLng(
place.geometry.location.lat(),
place.geometry.location.lng()
)
})
}
}
)
}
)
.catch(()=>{})
})
console.log(spa)
//レビューオブジェクトの型宣言関数
//実際は
function useDialog(){
//
const reviewer = reactive({name:'',src:'',content:''})
const setDialogData = (name:string,src:string,content:string) => {
reviewer.name = name
reviewer.src = src
reviewer.content = content
activateDialog.value = true
}
return{reviewer,setDialogData}
}
</script>
<style scoped>
略
</style>
コメント