🔗
Next.jsで簡易的な短縮URL機能を実装
URL短縮サービスは、長いURLを短くしてシェアしやすくするための便利なツールです。
Next.js App Routerを使用して、シンプルな短縮URL生成機能を実装してみます。
実装概要
ざっくり以下の流れで短縮URL機能を実装します。
- 短縮URLを生成: フォームにURLを入力し、短縮URLを生成してDBに保存。
- URLリストの表示: 保存済みの短縮URLを一覧で表示。
- リダイレクト処理: 短縮URLにアクセスした際、元のURLにリダイレクト。
実装コード
1. トップページ
src/app/page.tsx
import CreateShortUrl from "./_components/CreateShortUrl";
import { UrlList } from "./_components/UrlList";
export default function Home() {
return (
<div className="min-h-screen bg-gray-100 py-10">
<div className="max-w-4xl mx-auto space-y-10">
<h1 className="text-center text-2xl font-bold text-gray-800">
Short URL Generator
</h1>
<CreateShortUrl />
<UrlList />
</div>
</div>
);
}
2. 短縮URL生成フォーム
src/app/_components/CreateShortUrl.tsx
"use client";
import { useActionState } from "react";
import { createShortUrlAction } from "./action.server";
export default function CreateShortUrl() {
const [state, formAction, isPending] = useActionState(createShortUrlAction, null);
return (
<div className="bg-gray-100 p-6 border-2 border-black rounded-lg max-w-md mx-auto">
<h2 className="text-xl font-semibold mb-4 text-gray-900">Create Short URL</h2>
<form action={formAction} className="space-y-4">
<div>
<label className="block text-gray-700 font-medium mb-1">
Original URL:
</label>
<input
type="text"
name="originalUrl"
required
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-black focus:border-black"
placeholder="Enter your URL"
/>
</div>
<button
type="submit"
disabled={isPending}
className={`w-full py-2 px-4 font-semibold rounded-md shadow-md transition-colors duration-200 ${
isPending
? "bg-gray-300 text-gray-500 cursor-not-allowed"
: "bg-black text-white hover:bg-gray-800"
}`}
>
{isPending ? "Creating..." : "Create Short URL"}
</button>
</form>
{state && (
<div className="mt-4">
{state.body.shortUrl && (
<p className="text-gray-800 font-medium">
Short URL:{" "}
<a
href={state.body.shortUrl}
target="_blank"
className="underline text-black hover:text-gray-700"
>
{state.body.shortUrl}
</a>
</p>
)}
{state.body.error && (
<p className="text-red-600 font-medium">{state.body.error}</p>
)}
</div>
)}
</div>
);
}
3. 短縮URL生成アクション
src/app/_components/action.server.ts
"use server";
import { addShortUrl } from "@/server/lib/db/shorUrl";
import { customAlphabet } from "nanoid";
import { revalidatePath } from "next/cache";
const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789", 7);
export async function createShortUrlAction(_, formData: FormData) {
const originalUrl = formData.get("originalUrl") as string;
if (!originalUrl) {
return {
status: 400,
body: { error: "originalUrl is required" },
};
}
const shortId = nanoid();
const host = process.env.NEXT_PUBLIC_SHORT_URL_HOST || "http://localhost:3000";
const shortUrl = `${host}/r/${shortId}`;
try {
await addShortUrl(originalUrl, shortId);
} catch (error) {
console.error("Error adding short URL:", error);
return {
status: 500,
body: { error: "Failed to save short URL" },
};
}
revalidatePath("/");
return { status: 200, body: { shortUrl } };
}
- ランダムな短縮IDを生成し、リダイレクト先URLになる入力されたURLと一緒に保存します
4. URLリストの表示
src/app/_components/UrlList.tsx
import { getUrls } from "@/server/lib/db/shorUrl";
export async function UrlList() {
const urls = await getUrls();
const host = process.env.NEXT_PUBLIC_SHORT_URL_HOST || "http://localhost:3000";
return (
<div className="bg-gray-100 p-6 border-2 border-black rounded-lg max-w-md mx-auto mt-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900">URLs</h2>
<ul className="space-y-2">
{urls.map((url) => (
<li key={url.short_id} className="border p-4">
<a href={url.original_url}>{url.original_url}</a>
{" → "}
<a href={`${host}/r/${url.short_id}`}>{`${host}/r/${url.short_id}`}</a>
</li>
))}
</ul>
</div>
);
}
5. リダイレクト処理
src/app/r/[short]/page.tsx
import { getOriginalUrl } from "@/server/lib/db/shorUrl";
import { redirect } from "next/navigation";
export default async function RedirectPage({ params }: { params: { short: string } }) {
const originalUrl = await getOriginalUrl(params.short);
if (!originalUrl) {
redirect("/");
}
redirect(originalUrl);
}
- 短縮URLにアクセスすると、まずリダイレクト目的のページにアクセスします。
- paramsからshortIdを取得し、それに対応するオリジナルのURLをDBから取得してリダイレクトします
- getOriginalUrl関数内で、DBにクリックカウント用のレコードを記録することで、短縮URLのクリック数のカウントも簡単にできます
Discussion