【実務レベル】Next.jsを一般的な共有サーバーで公開する完全手順|SSG出力の注意点と対策

「Next.jsで作りたいが、すでにロリポップやエックスサーバーの契約がある」「Vercelに移すほど予算も運用も変えられない」。現場ではよく聞く話です。共有サーバーは基本的にPHP前提で、Node.jsを常駐させるSSRやISRは使えません。
結論から言うと、共有サーバーでNext.jsを公開するなら SSG(静的エクスポート)が前提です。output: 'export' でHTMLを書き出し、out を public_html に置く構成です。
この記事では、フリーランスのWebディレクター兼エンジニアとして、実際に共有サーバーへNext.jsを納品した手順と、そこで必ず踏む罠(画像最適化、API Routes、404)をまとめています。
なぜ共有サーバー × Next.js(SSG)なのか
VercelやCloudflareは便利ですが、既存契約を残したい案件では「今あるサーバーに静的ファイルを置く」方が早いです。
- サーバー契約を増やさず、固定費を抑えられる
- HTML配信なので表示が速い
- WordPressを残したまま、フロントだけモダン化できる
実務では「Nodeが動かない」は欠点ではなく、制約です。制約を先に設計に入れると、後からISR前提のコードを書いて詰むことが減ります。
方式 | 共有サーバー | 向いていること |
|---|---|---|
SSR / ISR | 不可(Node常駐が必要) | Vercelなど |
SSG( | 可 | コーポレート、ブログ、ポートフォリオ |
構築手順
1. 静的エクスポートを有効にする
App Routerでも Pages Routerでも、設定の核は同じです。共有サーバー向けは次の3点がセットです。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
distDir: "out",
trailingSlash: true,
images: {
unoptimized: true,
},
};
export default nextConfig;output: "export"… サーバーなしで動くHTMLを出すtrailingSlash: true…/about/をabout/index.htmlにする。共有サーバーの404対策でほぼ必須images.unoptimized: true… 後述するnext/imageの罠を避ける
2. ビルドして out を作る
pnpm build
# または
npm run build成功するとプロジェクト直下に out ができます。中身は index.html と各ページのフォルダです。このディレクトリだけをサーバーに上げます。node_modules は不要です。
{
"scripts": {
"build": "next build",
"export": "next build"
}
}3. 共有サーバーへアップロードする
アップロード先はサーバーによって名前が違いますが、公開ディレクトリはだいたい次のどれかです。
- mixhost:
public_html - エックスサーバー:
ドメイン名/public_html
実務での手順は次のとおりです。
- ローカルで
pnpm buildする - SFTP(FileZilla、Cyberduck、VS Code / Cursor のSFTP拡張)で接続する
outの中身をpublic_htmlにコピーする(outフォルダごとではなく中身)- ブラウザでトップと下層URLを確認する
out/about/index.htmlはサーバー上ではpublic_html/about/index.htmlです。outごと上げるとexample.com/out/になって事故ります。
SSHが使えるプランなら、手元でビルドして rsync する方が差分更新できて安全です。
pnpm build
rsync -avz --delete ./out/ user@example.com:~/public_html/現場でハマりがちなポイント
next/image がそのまま使えない
Next.jsの画像最適化は、実行時にNodeが画像を処理する仕組みです。SSGにはそのサーバーがありません。設定しないとビルドエラー、または本番で画像が壊れます。
実務では次のどちらかです。
images.unoptimized: trueにして、<Image>を普通のimgとして出す- microCMSやImgixのURLパラメータでリサイズする(例:
?w=800&q=80)
import Image from "next/image";
export default function Hero({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={450}
/>
);
}microCMSの画像なら、ドメイン許可は静的エクスポートではほぼ関係なくなります。unoptimized にした時点で最適化プロキシを通らないためです。代わりにCMS側のリサイズを使います。
const src = `${eyecatch.url}?w=800&q=80&fm=webp`;API Routes が使えない
app/api/ や pages/api/ はNodeサーバーです。静的エクスポートではビルド時点で弾かれます。問い合わせフォームをNextのAPIに投げていると、ここで初めて気づくことが多いです。
代替は次です。
- お問い合わせ: Formspree、Googleフォーム、サーバー側のPHP、またはWordPressのREST
- 記事取得: ビルド時にmicroCMSからfetchする(閲覧時にAPIを叩かない)
- Webhook再生成: 共有サーバー単体では難しい。GitHub Actionsでビルドしてrsyncする
import { createClient } from "microcms-js-sdk";
const client = createClient({
serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN!,
apiKey: process.env.MICROCMS_API_KEY!,
});
export async function getBlogs() {
return client.getList({
endpoint: "blogs",
queries: { limit: 10, orders: "-publishedAt" },
});
}APIキーはビルド環境(GitHub ActionsのSecrets)にだけ置きます。静的HTMLにキーを埋め込まないこと。クライアントコンポーネントからmicroCMSを直接叩くとキーが漏れます。
Trailing Slash を忘れると404になる
共有サーバーのApacheは、だいたい「ディレクトリ+index.html」でページを返します。trailingSlash: false のままだと、Nextは about.html を出します。一方ブラウザや内部リンクは /about や /about/ を要求し、サーバー側の設定と食い違って404になります。
実務の対策はシンプルです。
trailingSlash: trueにする- 内部リンクは
/about/に揃える - 必要なら
.htaccessで末尾スラッシュへ寄せる
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ /$1/ [L,R=301]動的ルートはビルド時に全部出す
generateStaticParams(App Router)または getStaticPaths(Pages Router)で、公開するURLをビルド時に列挙します。fallback: 'blocking' や ISRは共有サーバーでは動きません。記事を追加したら、もう一度ビルドしてアップロードが必要です。
import { getBlogs, getBlog } from "@/lib/microcms";
import { notFound } from "next/navigation";
export async function generateStaticParams() {
const { contents } = await getBlogs();
return contents.map((post) => ({ id: post.id }));
}
export default async function PostPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const post = await getBlog(id).catch(() => null);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}公開前チェックリスト
output: "export"とtrailingSlash: trueが入っているimages.unoptimized: trueが入っているapp/apiやpages/apiが残っていない- ビルドがローカルで通る
outの中身をpublic_html直下に置いている- 下層URL(
/posts/xxxx/)が404にならない
おすすめの共有サーバー
SSGは「HTMLを速く返せるか」が勝負です。次の系統は、静的配信との相性がよく、WordPress併用の案件でも使いやすいです。
- mixhost: 国内向けの管理画面が分かりやすく、小〜中規模のコーポレートに使いやすい
- エックスサーバー: 実績が多く、SSHやrsync運用まで見据えるなら候補に入りやすい
どちらも「Next.js専用」ではありません。PHPサイトと静的フロントを同じ契約で分けて置く、という使い方が実務では多いです。
既存WordPressからの移行・デプロイ支援
WordPressを残しつつフロントだけNext.jsにする、共有サーバーへSSGで載せる、画像とフォームの代替設計まで、設計から実装まで対応しています。「Vercelには出せないが、表示は速くしたい」という場合は特にこの構成が向いています。
ご相談はご相談・お問い合わせフォームからお気軽にどうぞ。サーバー契約を変えない前提で、移行手順から整理できます。