A Spotify clone project built with TypeScript
, React/Redux
, Nextjs
, TailwindCSS
, etc. Users can log into Spotify with NextAuth
and get Spotify's permissions and control API through spotify-web-api-node
. The general users can see their song list on this site, and Premium level users can control the playback of their songs and volume directly on this site.
❤️ This project is under development mode, so if you would like to try out this demo, please contact me and provide me with an email of your Spotify account. 😀
- Installation
- Login Spotify (NextAuth.js and Spotify-Web-Api-Node)
- Components
- State Management
- Acknowledgement
- install Next.js with Typescript
npx create-next-app@latest --ts spotify-clone
// or
npx create-next-app@latest -e with-typescript spotify-clone
- install
tailwindcss
https://tailwindcss.com/docs/guides/nextjs
- install
@reduxjs/toolkit
andreact-redux
npm install @reduxjs/toolkit react-redux
We use NextAuth
to login to Spotify and Spotify-Web-Api-Node
to get the user information.
- nvm node.js 16.13.1
- Spotify for Developers
- nextauth (
npm i next-auth
) - spotify-web-api-node (
npm i spotify-web-api-node
)
Spotify-Web-Api-Node
helps us to login Spotify and re-login withrefreshtoken
LOGIN_URL
is the login page the user will be redirected tospotifyApi
can refresh user'saccesstoken
, get user's contents- Source code
// lib/spotify.ts
import SpotifyWebApi from "spotify-web-api-node";
export const LOGIN_URL = `https://accounts.spotify.com/authorize?${new URLSearchParams(
params
).toString()}`;
const spotifyApi = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
});
export default spotifyApi;
NextAuth
contains dynamic route handler and all other global configurations, such as JWT secret key, custom pages, custom callbacks- Source code
providers
lets you place authentication platforms such as Github, Spotify, etc.
export default NextAuth({
providers: [
SpotifyProvider({
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
authorization: LOGIN_URL,
}),
// you can add more providers here...
],
})
async jwt
is a callback function that is invoked when JWT token is created or updated, the return token will be stored in a cookie- To make JWT token available in the browser, we need to pass the data in session callback (#2-3 callbacks session)
- Documentation
In our example:
- When user initial login, a token with addtional info such as User ID, OAuth Access Token is returned
- When user accesses with an unexpired token, the token itself is returned
- When user's token is expired, we use
spotifyApi
to refresh user's token
callbacks: {
async jwt({ token, account, user }) {
// initial sign in
if (account && user) {
return {
...token,
accessToken: account.access_token,
refreshToken: account.refresh_token,
userId: account.providerAccountId,
accessTokenExpires: account.expires_at! * 1000,
};
}
// accessToken still valid
if (Date.now() < (token as SpotifyJWT).accessTokenExpires * 1000) {
return token;
}
// accessToken expired, refresh it
return await refreshAccessToken(token as SpotifyJWT);
},
},
- When using JWT for sessions, the JWT payload is provided here for further delivery to the client
- Documentation
callbacks: {
...,
async session({ session, token }) {
session.accessToken = (token as SpotifyJWT).accessToken;
session.refreshToken = (token as SpotifyJWT).refreshToken;
session.userId = (token as SpotifyJWT).userId;
return session;
},
}
useProvider
ingetServerSideProps
gets theproviders
(e.g. Spotify) defined inNextAuth
and pass them to the clientLoginPage
renders login button for eachproviders
(in our case, we only have one provider - Spotify)- You need to set
Redirect URIs
tohttp://localhost:3000/api/auth/callback/spotify
in Spotify for Developer - Documentation for
signIn()
- Source Code
const LoginPage = ({ providers }: Props) => {
...
{Object.values(providers).map((provider) => (
<div key={provider.name}>
<button
onClick={() =>
signIn(provider.id, { callbackUrl: "/" })
}>
Login with {provider.name}
</button>
</div>
))}
...
};
export async function getServerSideProps() {
const providers = await getProviders();
return {
props: {
providers,
},
};
}
- Before we can access token in client side with
useSession()
, or in server side withgetSession()
, we have to setupSessionProvider
in the_app
- Documentation
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}
- Now we can use
useSession()
to access the token in the react client - Documentation
import { useSession } from "next-auth/react";
const Home = () => {
const { data: session, status } = useSession();
...
}
- We can use middleware to redirect users who are not logged in to the login page
- NextAuth getToken Documentation
- Nextjs Middleware Documentation
- Source Code
export async function middleware(req: any) {
const secret = process.env.JWT_SECRET!;
const token = await getToken({ req, secret });
const { pathname, origin } = req.nextUrl;
if (token || pathname.includes("/api/auth") || pathname === "/login") {
return NextResponse.next();
}
if (!token) {
return NextResponse.redirect(origin + "/login");
}
}
- Use
getSession()
in SSR can pre-fetch authorization from Spotify - Source Code
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
return {
props: { session },
};
};
- redux store
- playlist
- track