TypeScript React hooks and Next.js API route handlers for the SoundCloud API. Works with App Router and Pages Router. OAuth PKCE authentication built in. Client secrets stay on the server.
API Docs · Built on soundcloud-api-ts — the TypeScript-first SoundCloud API client.
When to use this vs direct API calls: Use this package when you're building a Next.js app and want typed React hooks for SoundCloud data without exposing credentials to the browser. The hooks fetch through your Next.js API routes, keeping secrets server-side. Supports both App Router and Pages Router. For backend-only or non-React projects, use soundcloud-api-ts directly.
npm install soundcloud-api-ts-next
1. Create API routes — secrets stay server-side:
// app/api/soundcloud/[...route]/route.ts
import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
const sc = createSoundCloudRoutes({
clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
redirectUri: process.env.SOUNDCLOUD_REDIRECT_URI, // for OAuth
});
const handler = sc.handler();
export const GET = handler;
export const POST = handler;
export const DELETE = handler;
// pages/api/soundcloud/[...route].ts
import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
const sc = createSoundCloudRoutes({
clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
});
export default sc.pagesHandler();
If your app stores OAuth tokens externally instead of using client credentials:
import { createSoundCloudRoutes } from "soundcloud-api-ts-next/server";
import { getRedisClient } from "../lib/redis";
const sc = createSoundCloudRoutes({
clientId: process.env.SOUNDCLOUD_CLIENT_ID!,
clientSecret: process.env.SOUNDCLOUD_CLIENT_SECRET!,
getToken: async () => {
const redis = await getRedisClient();
return redis.get("soundcloud:access_token");
},
});
export default sc.pagesHandler();
When getToken is set, it's called for every public route instead of the built-in client credentials flow. Auth routes (/me/*, actions) still use the Authorization: Bearer header from the request.
2. Add the provider:
// app/layout.tsx
import { SoundCloudProvider } from "soundcloud-api-ts-next";
export default function Layout({ children }) {
return (
<SoundCloudProvider apiPrefix="/api/soundcloud">
{children}
</SoundCloudProvider>
);
}
3. Use hooks:
import { useTrackSearch, usePlayer } from "soundcloud-api-ts-next";
function SearchPage() {
const { data: tracks, loading } = useTrackSearch("lofi beats");
if (loading) return <p>Searching...</p>;
return tracks?.map((track) => (
<div key={track.id}>
<p>{track.title} — {track.user.username}</p>
</div>
));
}
All hooks return { data, loading, error }.
| Hook | Description |
|---|---|
useResolve(url) |
Resolve a SoundCloud URL to a track, user, or playlist |
| Hook | Description |
|---|---|
useTrack(id) |
Single track |
useTrackSearch(query) |
Search tracks |
useTrackComments(id) |
Track comments |
useTrackLikes(id) |
Users who liked a track |
useRelatedTracks(id) |
Related tracks |
usePlayer(streamUrl) |
Audio player — { playing, progress, duration, play, pause, toggle, seek } |
| Hook | Description |
|---|---|
useUser(id) |
Single user |
useUserSearch(query) |
Search users |
useUserTracks(id) |
User's tracks |
useUserPlaylists(id) |
User's playlists |
useUserLikes(id) |
User's liked tracks |
useUserFollowers(id) |
User's followers |
useUserFollowings(id) |
User's followings |
| Hook | Description |
|---|---|
usePlaylist(id) |
Single playlist |
usePlaylistSearch(query) |
Search playlists |
usePlaylistTracks(id) |
Playlist tracks |
Cursor-based pagination with loadMore() and reset(). All return InfiniteResult<T>:
{ data: T[], loading, error, hasMore, loadMore, reset }
import { useInfiniteTrackSearch } from "soundcloud-api-ts-next";
function Feed() {
const { data, loading, hasMore, loadMore } = useInfiniteTrackSearch("dubstep");
return (
<>
{data.map((track) => <TrackCard key={track.id} track={track} />)}
{hasMore && <button onClick={loadMore} disabled={loading}>Load More</button>}
</>
);
}
| Hook | Description |
|---|---|
useInfiniteTrackSearch(query) |
Paginated track search |
useInfiniteUserSearch(query) |
Paginated user search |
useInfinitePlaylistSearch(query) |
Paginated playlist search |
useInfiniteUserTracks(id) |
User's tracks |
useInfiniteUserPlaylists(id) |
User's playlists |
useInfiniteUserLikes(id) |
User's liked tracks |
useInfiniteUserFollowers(id) |
User's followers |
useInfiniteUserFollowings(id) |
User's followings |
useInfiniteTrackComments(id) |
Track comments |
useInfinitePlaylistTracks(id) |
Playlist tracks |
Full OAuth 2.1 with PKCE via secure.soundcloud.com. No secrets on the client.
import { useSCAuth } from "soundcloud-api-ts-next";
function LoginButton() {
const { isAuthenticated, user, login, logout } = useSCAuth();
if (isAuthenticated) {
return (
<div>
<p>Welcome, {user?.username}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
return <button onClick={login}>Login with SoundCloud</button>;
}
// app/callback/page.tsx
"use client";
import { useEffect } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { useSCAuth } from "soundcloud-api-ts-next";
export default function Callback() {
const params = useSearchParams();
const router = useRouter();
const { handleCallback } = useSCAuth();
useEffect(() => {
const code = params.get("code");
const state = params.get("state");
if (code && state) {
handleCallback(code, state).then(() => router.push("/"));
}
}, [params]);
return <p>Authenticating...</p>;
}
Available after login. Automatically pass the user's access token.
| Hook | Description |
|---|---|
useMe() |
Current user profile |
useMeTracks() |
Your tracks |
useMeLikes() |
Your liked tracks |
useMePlaylists() |
Your playlists |
useMeFollowings() |
Who you follow |
useMeFollowers() |
Your followers |
Mutation hooks for authenticated users.
| Hook | Methods |
|---|---|
useLike() |
likeTrack(id), unlikeTrack(id) |
useFollow() |
follow(userId), unfollow(userId) |
useRepost() |
repostTrack(id), unrepostTrack(id) |
import { useLike, useFollow } from "soundcloud-api-ts-next";
function TrackActions({ trackId, artistId }) {
const { likeTrack } = useLike();
const { follow } = useFollow();
return (
<>
<button onClick={() => likeTrack(trackId)}>❤️ Like</button>
<button onClick={() => follow(artistId)}>➕ Follow</button>
</>
);
}
The catch-all handler exposes these routes automatically:
| Route | Method | Description |
|---|---|---|
/resolve?url= |
GET | Resolve a SoundCloud URL to an API resource |
/search/tracks?q= |
GET | Search tracks |
/search/users?q= |
GET | Search users |
/search/playlists?q= |
GET | Search playlists |
/tracks/:id |
GET | Track details |
/tracks/:id/stream |
GET | Stream URLs |
/tracks/:id/comments |
GET | Track comments |
/tracks/:id/likes |
GET | Track likes |
/tracks/:id/related |
GET | Related tracks |
/tracks/:id/like |
POST/DELETE | Like/unlike (auth) |
/tracks/:id/repost |
POST/DELETE | Repost/unrepost (auth) |
/users/:id |
GET | User details |
/users/:id/tracks |
GET | User tracks |
/users/:id/playlists |
GET | User playlists |
/users/:id/likes/tracks |
GET | User likes |
/users/:id/followers |
GET | User followers |
/users/:id/followings |
GET | User followings |
/playlists/:id |
GET | Playlist details |
/playlists/:id/tracks |
GET | Playlist tracks |
/playlists/:id/like |
POST/DELETE | Like/unlike (auth) |
/playlists/:id/repost |
POST/DELETE | Repost/unrepost (auth) |
/me |
GET | Current user (auth) |
/me/tracks |
GET | Your tracks (auth) |
/me/likes |
GET | Your likes (auth) |
/me/playlists |
GET | Your playlists (auth) |
/me/followings |
GET | Your followings (auth) |
/me/followers |
GET | Your followers (auth) |
/me/follow/:userId |
POST/DELETE | Follow/unfollow (auth) |
/auth/login |
GET | OAuth URL (PKCE) |
/auth/callback |
GET | Token exchange |
/auth/refresh |
POST | Refresh token |
/auth/logout |
POST | Sign out |
/next?url= |
GET | Pagination cursor |
Routes marked (auth) require Authorization: Bearer <token> header.
Re-exported from soundcloud-api-ts:
import type {
SoundCloudTrack,
SoundCloudUser,
SoundCloudPlaylist,
SoundCloudComment,
SoundCloudStreams,
SoundCloudToken,
} from "soundcloud-api-ts-next";
MIT