Traditional URL shorteners rely on centralized services, making them vulnerable to censorship, data breaches, and single points of failure. A decentralized, Web3-driven URL shortener addresses these issues by storing link mappings on the blockchain, ensuring immutability, transparency, and censorship resistance.
In this guide, we’ll build a fully decentralized URL-shortening service using Next.js, Ethereum smart contracts, and Fleek’s edge-optimized hosting. By the end, you’ll have a streamlined Next.js app that enables users to shorten, store, and resolve URLs seamlessly.
Key Benefits:
Ensure you have:
npx create-next-app@latest
Project name? web3-url-shortener Use TypeScript? No Use ESLint? No Use Tailwind CSS? Yes Use `src/` directory? Yes Use App Router? No Use Turbopack? No Customize import alias? No
npm install wagmi ethers @tanstack/react-query @rainbow-me/rainbowkit # fleek-next adapter npm install @fleek-platform/next
fleek login
npm run dev
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract UrlShortener { // Maps a short code (e.g. "abc123") to a full URL mapping(string => string) private shortToLong; event URLShortened(string indexed shortCode, string longUrl); /** * @notice Create a shortened URL by mapping a short code to a long URL. * @param shortCode The short code (unique identifier) * @param longUrl The long URL to map to */ function setURL(string calldata shortCode, string calldata longUrl) external { require(bytes(shortCode).length > 0, "Short code cannot be empty"); require(bytes(longUrl).length > 0, "Long URL cannot be empty"); // In a production scenario, you'd probably want some uniqueness checks, // or handle collisions differently. For now we allow overwriting. shortToLong[shortCode] = longUrl; emit URLShortened(shortCode, longUrl); } /** * @notice Retrieve the long URL for a given short code. * @param shortCode The short code to look up * @return longUrl The long URL that the short code points to */ function getURL(string calldata shortCode) external view returns (string memory) { return shortToLong[shortCode]; } }
The above UrlShortener smart contract allows users to create and manage shortened URLs. It maps unique short codes to long URLs, enabling efficient URL storage and retrieval. Users can set a mapping using the setURL function and retrieve the original URL with getURL. The contract includes basic validations and emits an event when a new URL is shortened. I deployed my contract already and the address is: 0x2729D62B3cde6fd2263dF5e3c6509F87C6C05892
Create a .env in the project root:
npx create-next-app@latest
Create src/abi/URLShortener.json with:
Project name? web3-url-shortener Use TypeScript? No Use ESLint? No Use Tailwind CSS? Yes Use `src/` directory? Yes Use App Router? No Use Turbopack? No Customize import alias? No
In src/lib/contract.js:
npm install wagmi ethers @tanstack/react-query @rainbow-me/rainbowkit # fleek-next adapter npm install @fleek-platform/next
fleek login
Replace {{REOWN-PROJECT-ID}} and {{REOWN-APP-NAME}} with your details from Reown.
Providers Setup:
Below, I show how to set up web3 providers properly in a Next.js application to handle client-side rendering correctly.
The key is splitting the providers into two parts to safely handle web3 functionality that must run only in the browser.
Create src/lib/providers.js:
npm run dev
Create src/lib/Web3Providers.jsx:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract UrlShortener { // Maps a short code (e.g. "abc123") to a full URL mapping(string => string) private shortToLong; event URLShortened(string indexed shortCode, string longUrl); /** * @notice Create a shortened URL by mapping a short code to a long URL. * @param shortCode The short code (unique identifier) * @param longUrl The long URL to map to */ function setURL(string calldata shortCode, string calldata longUrl) external { require(bytes(shortCode).length > 0, "Short code cannot be empty"); require(bytes(longUrl).length > 0, "Long URL cannot be empty"); // In a production scenario, you'd probably want some uniqueness checks, // or handle collisions differently. For now we allow overwriting. shortToLong[shortCode] = longUrl; emit URLShortened(shortCode, longUrl); } /** * @notice Retrieve the long URL for a given short code. * @param shortCode The short code to look up * @return longUrl The long URL that the short code points to */ function getURL(string calldata shortCode) external view returns (string memory) { return shortToLong[shortCode]; } }
Modify _app.js:
In pages/_app.js:
NEXT_PUBLIC_CONTRACT_ADDRESS=0x2729D62B3cde6fd2263dF5e3c6509F87C6C05892 NEXT_PUBLIC_RPC_URL={{YOUR-ARBITRUM-SEPOLIA-RPC-URL}}
This page handles connecting a wallet, entering a long URL and short code, and writing to the blockchain. We will do a similar split to what we did above. The key reasons for this split:
In pages/index.js:
{ "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "string", "name": "shortCode", "type": "string" }, { "indexed": false, "internalType": "string", "name": "longUrl", "type": "string" } ], "name": "URLShortened", "type": "event" }, { "inputs": [{ "internalType": "string", "name": "shortCode", "type": "string" }], "name": "getURL", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "shortCode", "type": "string" }, { "internalType": "string", "name": "longUrl", "type": "string" } ], "name": "setURL", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] }
Create src/lib/URLShortenerApp.jsx:
import { ethers } from "ethers"; import urlShortenerJson from "../abi/URLShortener.json"; export function getSignerContract(signer) { if (!signer) { console.error("No signer provided to getSignerContract"); throw new Error("No signer available"); } const address = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS; if (!address) { throw new Error("Contract address not configured"); } return new ethers.Contract(address, urlShortenerJson.abi, signer); }
One final thing is to ensure that your tailwind.config.js matches the below:
import { http} from "wagmi"; import { arbitrumSepolia } from "wagmi/chains"; import { getDefaultConfig } from "@rainbow-me/rainbowkit"; const projectId = {{REOWN-PROJECT-ID}}; export const config = getDefaultConfig({ appName: {{REOWN-APP-NAME}}, projectId: projectId, chains: [arbitrumSepolia], transports: { [arbitrumSepolia.id]: http(), }, ssr: false, });
For server-side and dynamic routes, ensure you have: export const runtime = 'edge' within the files.
1.Build the Application:
"use client"; import dynamic from "next/dynamic"; import { useEffect, useState } from "react"; const Web3Providers = dynamic(() => import("./Web3Providers"), { ssr: false, }); export default function Providers({ children }) { const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) { return <>{children}</>; } return <Web3Providers>{children}</Web3Providers>; }
This generates a .fleek directory.
2.Create a Fleek Function:
// Web3Providers.jsx "use client"; import { WagmiProvider } from "wagmi"; import "@rainbow-me/rainbowkit/styles.css"; import { RainbowKitProvider, darkTheme } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { config } from "../lib/wagmi"; export default function Web3Providers({ children }) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false, }, }, }); return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <RainbowKitProvider theme={darkTheme({ accentColor: "#0E76FD", accentColorForeground: "white", borderRadius: "large", fontStack: "system", overlayBlur: "small", })} > {children} </RainbowKitProvider> </QueryClientProvider> </WagmiProvider> ); }
3.Name your function (e.g., web3-url-shortener-next-js).
4.Deploy to Fleek:
import "../styles/globals.css"; import "@rainbow-me/rainbowkit/styles.css"; import Providers from "../lib/providers"; function App({ Component, pageProps }) { return ( <Providers> <Component {...pageProps} /> </Providers> ); } export default App;
After a successful deployment, Fleek will provide a URL to access your application.
You’ve successfully built and deployed a decentralized URL shortener that:
This foundation can be extended or integrated into larger Next.js Apps. Experiment with custom UI, track analytics, or integrate other smart contracts to enhance your Web3 URL shortener. View what the final result should look like here: https://shortener.on-fleek.app/
You can go to the Github repo to view the full code: https://github.com/tobySolutions/shortener
This was originally published on the Fleek blog
The above is the detailed content of Building a WebRL shortener with Next.js on Fleek. For more information, please follow other related articles on the PHP Chinese website!