Fix: Module is Only Available Server-side in Astro

Error message:
Module is only available server-side.
Build & Compilation 2025-01-25

What Causes This Error?

This error occurs when you try to import a server-only module (like database clients, file system modules, or server utilities) in client-side code. Some modules can only run on the server.

The Problem

---
import { db } from '../lib/database';
---

<script>
  // ❌ Can't use server module in client script
  import { db } from '../lib/database';
  const data = await db.query('SELECT * FROM users');
</script>

The Fix

Keep Server Code in Frontmatter

---
// ✅ Server code in frontmatter
import { db } from '../lib/database';
const users = await db.query('SELECT * FROM users');
---

<ul>
  {users.map(user => <li>{user.name}</li>)}
</ul>

Use API Routes for Client Access

// src/pages/api/users.ts
import { db } from '../../lib/database';

export async function GET() {
  const users = await db.query('SELECT * FROM users');
  return Response.json(users);
}
---
// Page without server imports
---

<ul id="users"></ul>

<script>
  // ✅ Fetch from API instead
  const response = await fetch('/api/users');
  const users = await response.json();

  const list = document.getElementById('users');
  users.forEach(user => {
    const li = document.createElement('li');
    li.textContent = user.name;
    list.appendChild(li);
  });
</script>

Common Scenarios

Database Clients

---
// ✅ Database queries in frontmatter only
import { prisma } from '../lib/prisma';
const posts = await prisma.post.findMany();
---

<!-- Don't import prisma in <script> tags -->

File System Access

---
// ✅ File operations in frontmatter
import fs from 'node:fs';
const content = fs.readFileSync('./data.json', 'utf-8');
---

<!-- fs is not available in browser -->

Environment Variables

---
// ✅ Server-side env vars in frontmatter
const apiKey = import.meta.env.API_SECRET_KEY;
const data = await fetchWithKey(apiKey);
---

<script>
  // ❌ Can't access server env vars
  // const key = import.meta.env.API_SECRET_KEY;

  // ✅ Use public env vars prefixed with PUBLIC_
  const publicKey = import.meta.env.PUBLIC_API_KEY;
</script>

Marking Server-Only Modules

// src/lib/database.ts
import 'server-only'; // Explicitly mark as server-only

import { Pool } from 'pg';

export const db = new Pool({
  connectionString: process.env.DATABASE_URL,
});

Shared Types (OK to Import)

// src/types/user.ts
// ✅ Types can be imported anywhere
export interface User {
  id: string;
  name: string;
}
<script>
  // ✅ Type imports are fine (erased at runtime)
  import type { User } from '../types/user';

  const users: User[] = await fetch('/api/users').then(r => r.json());
</script>

Pass Data to Client via Props

---
import { db } from '../lib/database';
const initialData = await db.getData();
---

<div id="app" data-initial={JSON.stringify(initialData)}></div>

<script>
  // ✅ Read server data from DOM
  const el = document.getElementById('app');
  const initialData = JSON.parse(el.dataset.initial);
</script>

Framework Components

// src/components/DataDisplay.jsx
// ❌ Can't import server modules in framework components

// ✅ Pass data as props
export default function DataDisplay({ data }) {
  return <ul>{data.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
---
import { db } from '../lib/database';
import DataDisplay from '../components/DataDisplay.jsx';

const data = await db.query('...');
---

<DataDisplay client:load data={data} />

Quick Checklist

  • Database/file system code goes in frontmatter only
  • Use API routes for client-server communication
  • Pass data to client via props or data attributes
  • Type imports are always OK
  • Use PUBLIC_ prefix for client env vars