In this workshop you'll learn how to use Next.js, TypeScript, Lens Protocol, and the Lens SDK to build out a social application.
The app we'll be building will have the following features:
- When the app loads, it will render a list of recommended users from the Lens API along with their profile picture and bio
- When we click on a user, we will navigate to a detail view where we will see all of their publications as well as more profile details
- The user profile view will also have the option for a user to sign in and follow another user.
By the end of this tutorial you'll have a good understanding of how to get started building on Lens with TypeScript and the Lens SDK.
To get started, create a new Next.js application:
npx create-next-app lens-app
âś” Would you like to use TypeScript with this project? Yes
âś” Would you like to use ESLint with this project? Yes
âś” Would you like to use Tailwind CSS with this project? Yes
âś” Would you like to use `src/` directory with this project? No
âś” Use App Router (recommended)? Yes
âś” Would you like to customize the default import alias? No
Next, change into the new directory and install the following dependencies:
npm install @lens-protocol/react-web@alpha @lens-protocol/wagmi@alpha ethers@legacy-v5 wagmi viem @web3modal/wagmi
Next, update the tsconfig.json
and add the following to the compilerOptions
configuration:
"noImplicitAny": false,
Go to https://cloud.walletconnect.com/app and create a project, then copy the Project ID.
Create a file named .env.local
in the root directory, adding the following code:
NEXT_PUBLIC_WC_ID="<your-project-id>"
Create a file in the app
directory named Web3ModalProvider.tsx
and add the following code:
"use client"
import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'
import { WagmiConfig } from 'wagmi'
import { arbitrum, mainnet } from 'viem/chains'
const projectId = process.env.NEXT_PUBLIC_WC_ID || ''
const metadata = {
name: 'Web3Modal',
description: 'Web3Modal Example',
url: 'https://web3modal.com',
icons: ['https://avatars.githubusercontent.com/u/37784886']
}
const chains = [mainnet, arbitrum]
const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata })
createWeb3Modal({ wagmiConfig, projectId, chains })
export function Web3ModalProvider({ children }) {
return <WagmiConfig config={wagmiConfig}>{children}</WagmiConfig>
}
Next, create a file in the app
directory named LensProvider.tsx
and add the following code:
'use client'
import { LensConfig, production } from '@lens-protocol/react-web'
import { bindings as wagmiBindings } from '@lens-protocol/wagmi'
import { LensProvider as Provider } from '@lens-protocol/react-web'
const lensConfig: LensConfig = {
bindings: wagmiBindings(),
environment: production,
}
export function LensProvider({ children }) {
return (
<Provider config={lensConfig}>
{ children }
</Provider>
)
}
Next, we want to configure our app to use the providers.
This is typically done at the entrypoint to the app, and only needs to be done once.
Update app/layout.tsx
with the following code:
// app/layout.tsx
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { LensProvider } from './LensProvider'
import { Web3ModalProvider } from './Web3ModalProvider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<Web3ModalProvider>
<LensProvider>
{children}
</LensProvider>
</Web3ModalProvider>
</body>
</html>
)
}
Next, let's query for profiles and render them in our app.
To do so, open app/page.tsx
and add the following code:
// app/page.tsx
'use client'
import Image from 'next/image'
import { useExploreProfiles, ExploreProfilesOrderByType } from '@lens-protocol/react-web'
import Link from 'next/link'
export default function Home() {
const { data } = useExploreProfiles({
orderBy: ExploreProfilesOrderByType.MostFollowers
})
return (
<div className='p-20'>
<h1 className='text-5xl'>My Lens App</h1>
{
data?.map((profile, index) => (
<Link
href={`/profile/${profile.handle?.localName}.${profile.handle?.namespace}`}
key={index}
>
<div className='my-14'>
{
profile.metadata?.picture?.__typename === 'ImageSet' ? (
<img
src={profile.metadata?.picture?.optimized?.uri || ''}
width="120"
height="120"
alt={profile.handle?.fullHandle || ''}
/>
) : <div className="w-28 h-28 bg-slate-500" />
}
<h3 className="text-3xl my-4">{profile.handle?.localName}.{profile.handle?.namespace}</h3>
<p className="text-xl">{profile.metadata?.bio}</p>
</div>
</Link>
))
}
</div>
)
}
In useExploreProfiles
, we are calling the Lens API to fetch a list of recommended profiles.
Once the profiles are returned, we map over them, rendering each profile with their profile details and a link to view the individual profile which we'll build out later.
To run the app, run the following command:
npm run dev
In the above code, we've added a link to each profile that, when clicked, will navigate to /profile/profile.id
. What we want to happen is that when a user navigates to that page, they are able to view more details about that profile.
We also want go give users a way to sign in and follow users.
This functionality does not yet exist, so let's create it.
In the app
directory, create a new folder named profile
.
In the profile
directory create a new folder named [handle]
.
In the [handle]
folder, create a new file named page.tsx
.
In this file, add the following code:
// app/profile/[handle]/page.tsx
'use client'
import {
useProfile, usePublications, Profile, LimitType, PublicationType
} from '@lens-protocol/react-web'
export default function Profile({ params: { handle }}) {
const namespace = handle.split('.')[1]
handle = handle.split('.')[0]
let { data: profile, loading } = useProfile({
forHandle: `${namespace}/${handle}`
})
if (loading) return <p className="p-14">Loading ...</p>
return (
<div>
<div className="p-14">
{
profile?.metadata?.picture?.__typename === 'ImageSet' && (
<img
width="200"
height="200"
alt={profile.handle?.fullHandle}
className='rounded-xl'
src={profile.metadata.picture.optimized?.uri}
/>
)
}
<h1 className="text-3xl my-3">{profile?.handle?.localName}.{profile?.handle?.namespace}</h1>
<h3 className="text-xl mb-4">{profile?.metadata?.bio}</h3>
{ profile && <Publications profile={profile} />}
</div>
</div>
)
}
function Publications({
profile
}: {
profile: Profile
}) {
let { data: publications } = usePublications({
where: {
publicationTypes: [PublicationType.Post],
from: [profile.id],
},
limit: LimitType.TwentyFive
})
return (
<>
{
publications?.map((pub: any, index: number) => (
<div key={index} className="py-4 bg-zinc-900 rounded mb-3 px-4">
<p>{pub.metadata.content}</p>
{
pub.metadata?.asset?.image?.optimized?.uri && (
<img
width="400"
height="400"
alt={profile.handle?.fullHandle}
className='rounded-xl mt-6 mb-2'
src={pub.metadata?.asset?.image?.optimized?.uri}
/>
)
}
</div>
))
}
</>
)
}
useProfile
allows you to get a user's profile details by passing in a Lens handle or profile ID
usePublications
allows you to fetch a user's publications by passing in a profile
To run the app, run the following command:
npm run dev
Next, let's add some additional functionality that will allow a user to sign and and then follow another user.
// app/profile/[handle]/page.tsx
'use client'
import {
useProfile,
usePublications,
Profile,
LimitType,
PublicationType,
useLogin,
useProfiles,
useFollow
} from '@lens-protocol/react-web'
import { useWeb3Modal } from '@web3modal/wagmi/react'
import { useAccount } from 'wagmi'
export default function Profile({ params: { handle }}) {
const namespace = handle.split('.')[1]
handle = handle.split('.')[0]
let { data: profile, loading } = useProfile({
forHandle: `${namespace}/${handle}`
})
const { open } = useWeb3Modal()
const { address, isConnected } = useAccount()
const { execute: login, data } = useLogin()
const { execute: follow } = useFollow();
const { data: ownedProfiles } = useProfiles({
where: {
ownedBy: [address || ''],
},
})
if (!profile) return null
if (loading) return <p className="p-14">Loading ...</p>
return (
<div>
<div className="p-14">
{
profile?.metadata?.picture?.__typename === 'ImageSet' && (
<img
width="200"
height="200"
alt={profile.handle?.fullHandle}
className='rounded-xl'
src={profile.metadata.picture.optimized?.uri}
/>
)
}
{
!isConnected && (
<button
className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
onClick={() => open()}>
Connect Wallet
</button>
)
}
{
!data && ownedProfiles && isConnected && (
<button
className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
onClick={() => login({
address: address || '',
profileId: ownedProfiles[ownedProfiles.length - 1].id
})}>
Login with Lens
</button>
)
}
{
data && profile.operations.canFollow !== 'NO' && (
<button
className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
onClick={() => profile ? follow({ profile: profile }) : null }>
Follow
</button>
)
}
<h1 className="text-3xl my-3">{profile?.handle?.localName}.{profile?.handle?.namespace}</h1>
<h3 className="text-xl mb-4">{profile?.metadata?.bio}</h3>
{ profile && <Publications profile={profile} />}
</div>
</div>
)
}
function Publications({
profile
}: {
profile: Profile
}) {
let { data: publications } = usePublications({
where: {
publicationTypes: [PublicationType.Post],
from: [profile.id],
},
limit: LimitType.TwentyFive
})
return (
<>
{
publications?.map((pub: any, index: number) => (
<div key={index} className="py-4 bg-zinc-900 rounded mb-3 px-4">
<p>{pub.metadata.content}</p>
{
pub.metadata?.asset?.image?.optimized?.uri && (
<img
width="400"
height="400"
alt={profile.handle?.fullHandle}
className='rounded-xl mt-6 mb-2'
src={pub.metadata?.asset?.image?.optimized?.uri}
/>
)
}
</div>
))
}
</>
)
}
When you run the app, you should now be able to sign in and follow another user.
Now that you've built your first basic application, it's time to explore more of the Lens API!
Consider diving into modules or learning about gasless transactions and the dispatcher.
Also consider adding the following features to your new app:
- Searching for users
- Creating a post