はじめに
こんにちは。HRBrainでオウンドメディアやランディングページの開発を担当している渡邉です。
HRBrainでは、サービスサイトの開発にAstroを採用しています。
Astroは、高速なウェブサイト構築を可能にするフレームワークであり、サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)を行うことができます。
この記事では、Astro開発で遭遇した window is not defined
というエラーの解決策を分かりやすく解説します。
Astroはサーバーサイドで実行される
Astroは、まずサーバーサイドでHTMLを生成し、それをブラウザに送信します。
その後、ブラウザ上でJavaScriptが実行され、イベントリスナーの登録やコンポーネントの状態管理などを行います。
これを「ハイドレーション」と呼びます。
window is not defined エラーの原因
ブラウザには window
というオブジェクトが用意されており、ブラウザのウィンドウに関する情報を持っています。
しかし、Astroはサーバーサイドレンダリングを行うため、コードの一部はサーバー側で実行されます。
サーバーにはブラウザのウィンドウは存在しないため、windowオブジェクトも存在しません。
そのため、サーバー側で実行されるコードの中でwindowオブジェクトを使おうとすると、window is not defined
エラーが発生します。
window is not defined エラーの解消
以下のAstroドキュメントにも記載されている方法ではありますが、コードを用いてより詳しく解説します。
client:only ディレクティブを使う
Astroは、React、Vue、Svelteなど、様々なフレームワークとの連携(インテグレーション)をサポートしています。
client:only
ディレクティブを使用することで、これらのフレームワークをクライアントサイドでのみ実行することができます。
例えば、以下のようにコンポーネントを client:only
ディレクティブを使ってレンダリングすることができます。
<Component client:only="react" /> <!-- React -->
<Component client:only="vue" /> <!-- Vue -->
<Component client:only="svelte" /> <!-- Svelte -->
client:only ディレクティブの効果
client:only
ディレクティブを指定したコンポーネントは、そのコンポーネント自身だけでなく、そのコンポーネント内で使用されている子コンポーネントも、すべてクライアントサイドでのみ実行されるようになります。
例えば、以下のような構成の場合、ParentComponent
と ChildComponent
はどちらもクライアントサイドでのみ実行されます。
<ParentComponent client:only="react" />
import React from "react";
import ChildComponent from "./ChildComponent";
function ParentComponent() {
return (
<div>
<h1>Parent Component</h1>
<ChildComponent />
</div>
);
}
export default ParentComponent;
import React from "react";
function ChildComponent() {
return <p>Child Component</p>;
}
export default ChildComponent;
VueのonMountedやReactのuseEffectを使う
クライアントサイドでのみ実行されるライフサイクルイベントを利用する方法もあります。
Vue.jsでは onMounted
、Reactでは useEffect
というライフサイクルイベントが用意されています。
-
onMounted
は、コンポーネントがブラウザにマウントされた後に実行されます。 -
useEffect
は、コンポーネントがブラウザにマウントされた後、そして必要に応じて更新後にも実行されます。
これらのイベントを使うことで、サーバー側でのエラーを防ぎつつ、クライアントサイドでwindowオブジェクトを安全に利用することができます。
Vueでの例 (onMountedフック)
<script setup>
import { ref, onMounted } from 'vue'
// ref()を使ってリアクティブな変数 currentPath を作成
const currentPath = ref(null)
onMounted(() => {
// ref()で作成した変数の値を更新するには .value を使用
currentPath.value = window.location.pathname;
})
</script>
<template>
<div>現在のパス: {{ currentPath }}</div>
</template>
Reactでの例 (useEffectフック)
import React, { useState, useEffect } from "react";
function CurrentPathComponent() {
const [currentPath, setCurrentPath] = useState(null);
useEffect(() => {
setCurrentPath(window.location.pathname);
}, []);
return <div>現在のパス: {currentPath}</div>;
}
export default CurrentPathComponent;
まとめ
Astroで window is not defined
というエラーが発生する原因は、サーバーサイドで windowオブジェクトが存在しないためです。
以下のいずれかの方法で解消することができます。
-
client:only
ディレクティブを使って、コンポーネントをクライアントサイドでのみ実行する - Vue.jsの
onMounted
やReactuseEffect
を使って、コンポーネントがクライアントサイドでマウントされた後にwindowオブジェクトにアクセスする
PR
HRBrainではフロントエンドエンジニア(コミュニケーションデザイン)の採用も行なっているので、ぜひ!