# Can I make server side programming in Nextra?
You can perform server-side programming in Nextra, since it’s built on top of Next.js. This means Nextra inherits all of Next.js’s core features, including
- server-side rendering (SSR),
- API routes, and
- server-side data fetching methods (like
getServerSideProps
).
# How Server-Side Programming Works in Nextra
API Routes: You can define API routes in a Nextra project just like in a regular Next.js project. Place your API files inside the
pages/api
directory to create serverless functions or API endpoints. These endpoints can handle server-side logic, database operations, authentication, and more.Server-Side Rendering (SSR): Nextra supports SSR via
getServerSideProps
in custom pages. However, since Nextra is generally used for static documentation sites or blogs, SSR might be less common, as static generation is usually preferred for speed and simplicity. Still, for pages that require dynamic data fetching on the server, you can addgetServerSideProps
to retrieve data at request time.Data Fetching with Static Site Generation (SSG): Since Nextra is optimized for static content, we can use SSG with
getStaticProps
, where data is fetched at build time. See the example at ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/pages/get-static-props.mdx (opens new window) at branchall-repos
. This can be useful for loading content or configuration data that doesn’t change often. But if you need fresh data on each request, SSR can be implemented as well.
See also Dynamic MDX with Nextra for an example of dynamic MDX content with Nextra.
# Adding Server-Side Logic to a Nextra Page
Example: suppose you want to fetch all the GitHub public repos of a user and show them dynamically on a Nextra page.
You can find a solution at branch allrepos
of repo ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865 (opens new window). Here are the involved files:
├── components
│ └── ask-user.jsx
├── pages
│ ├── api
│ │ └── github-repos
│ │ └── [username].js
│ ├── user
│ │ └── [username].jsx
│ └── userRepos.mdx
└── theme.config.jsx
2
3
4
5
6
7
8
9
10
- The code at pages/userRepos.mdx (opens new window)
- The
ask-user.jsx
component code at file ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/components/ask-user.jsx (opens new window) - The dynamic page file pages/user/[username].jsx (opens new window) at branch
allrepos
: - The server code at file pages/api/github-repos/[username].js (opens new window)
- See the GitHub Docs chapter Using pagination in the REST API (opens new window)
# An API endpoint handler: pages/api/github-repos/[username].js
The square brackets [ ]
in the file name pages/api/github-repos/[username].js (opens new window)ndicate a dynamic segment.
The file is located in the pages/api
directory, which is where API route files to be, defining an API endpoint
so that a request like GET /api/github-repos/johndoe
will be handled by this file and the username
parameter will be johndoe
.
The function handler(req, res)
fetches all public repositories from the REST GitHub API for a
given username
, one page at a time.
export default async function handler(req, res) {
const { username } = req.query
if (!username || typeof username !== 'string') {
return res.status(400).json({ error: 'Username is required' })
}
// Exercise: Receive the page number and fetch one page repos at a time
// and modify the <AskUser /> to have buttons next, prev, etc.
try {
let allRepos = []
let page = 1
let hasNextPage = true
while (hasNextPage) {
// const response = await fetch(`https://api.github.com/users/${username}/repos`)
const response = await fetch(`https://api.github.com/users/${username}/repos?per_page=100&page=${page}`)
if (!response.ok) {
throw new Error('Failed to fetch repos')
}
const repos = await response.json()
allRepos = [...allRepos, ...repos]
const linkHeader = response.headers.get('Link')
hasNextPage = linkHeader ? linkHeader.includes('rel="next"') : false
page++
}
res.status(200).json(allRepos)
} catch (error) {
console.error('Error fetching repos:', error)
res.status(500).json({ error: 'Failed to fetch repos '+error.message })
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
response.headers.get('Link')retrieves the Link header from the
response`, which contains pagination information.
hasNextPage = linkHeader ? linkHeader.includes('rel="next"') : false
checks if the Link header contains a rel="next"
link, indicating that there are more pages to fetch.
See the GitHub Docs chapter Using pagination in the REST API (opens new window).
The while loop fetches all the pages of repositories for the given username
and concatenates them into the allRepos
array.
It can be substituted by a generator using a for await ... of
loop like is described in my comment at the StackOverflow question for await of VS Promise.all (opens new window)
Another exercise is to modify this example to receive the page number and fetch one page repos at a time.
In such case you have also to modify the <AskUser />
to have buttons next
, prev
, etc.
# The userRepos.mdx
page
This quite a simple mdx
page that imports the component to ask the user for a GitHub username and then fetches the user's public repositories using the API endpoint handler described above.
import AskUser from '@/components/ask-user'
<AskUser />
2
3
# The ask-user.jsx
code
import {
useState,
useRef, // The `useRef` hook allows to create a mutable reference that persists across component re-renders without causing the component to re-render when its value changes
useEffect } from 'react'
import { useRouter } from 'next/router'
import styles from '@/components/Home.module.css'
export default function AskUser() {
const [username, setUsername] = useState('')
const router = useRouter()
const inputRef = useRef(null)
// For when the page is called directly with something like
// http://localhost:3000/userRepos?username=rmz01
// This is useful in scenarios where the page can be accessed directly with a query parameter or after a form submission.
router.query.username && router.push(`/user/${router.query.username}`)
useEffect(() => {
if (inputRef.current) { // inputRef.current refers to the DOM element that inputRef is attached to.
inputRef.current.focus()
}
}, [])
const handleSubmit = (e) => {
e.preventDefault() // prevent the page to reload
if (username) {
router.push(`/user/${username}`)
}
}
return (
<div className={styles.container}>
<form onSubmit={handleSubmit} className={styles.form}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter GitHub username"
className={styles.input}
ref={inputRef}
/>
<button type="submit" className={styles.button}>
View Repos
</button>
</form>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
The line 27 router.push(/user/${username})
uses the router.push
method to navigate to the URL /user/${username}
. This request will be handled by the dynamic page file pages/user/[username].jsx
.
# The dynamic page file pages/user/[username].jsx
This file is a dynamic page file that fetches the user's public repositories using the API endpoint handler described above.
// Issue Server-side Rendering (SSR) support #181
// https://github.com/shuding/nextra/issues/181#issuecomment-1399318974
import Link from 'next/link'
import styles from '@/components/UserRepos.module.css'
export const getServerSideProps = async (context) => {
const username = context.params?.username
console.error(username)
try {
const URL = `http://localhost:3000/api/github-repos/${username}`;
console.error(`**** ${new Date()} URL = ${URL} *******`)
const res = await fetch(URL)
if (!res.ok) {
console.error('**** Failed to fetch repos')
throw new Error('Failed to fetch repos')
}
const repos = await res.json()
console.error(JSON.stringify(repos[0], null, 2))
return {
props: {
repos,
username,
reason: false
},
}
} catch (error) {
console.error('Error in getServerSideProps:', error)
return {
props: {
repos: [],
username,
reason: error.message
},
}
}
}
export default function UserRepos(P) {
const str2 = JSON.stringify(P, null, 2);
console.error(`********** ${str2} ************`);
const { repos, username, reason } = P;
return (
<div className={styles.container}>
<h1 className={styles.title}>{username}'s Repositories</h1>
{(<>
<ol className={styles.nlist}>
{repos.map((repo) => (
<li key={repo.id} className={styles.listItem}>
<a href={repo.html_url} target="_blank" rel="noopener noreferrer" className={styles.link}>
{repo.name}
</a>
</li>
))}
</ol>
<p>{reason && (`Something went wrong: "${reason}"`)}</p>
</>
)}
<Link href="/userRepos" className={styles.backLink}>
Back to GitHub Repository Viewer
</Link>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
In line 6 we declare getServerSideProps
which receives the context
. In Next.js the term context typically refers to an object that provides information about the request and environment in which a function is running. It is commonly used in data fetching methods like getStaticProps
, getServerSideProps
, and API route handlers. In getServerSideProps
rovides parameters like params
, req
, res
, query
, and resolvedUrl
.
The getServerSideProps
function fetches the user's public repositories from the GitHub API using the username
parameter from the URL. It returns the fetched repositories as props
to the UserRepos
component. These properties are passed as props
to the page component, (lines 42 and 45) allowing it to render dynamic content based on the fetched data and provided values (lines 47-65).
# Limitations
While Nextra allows for server-side programming, it’s optimized for static content. As a result:
- Using too much server-side logic might go against Nextra’s goal of providing fast, statically generated content.
- Some out-of-the-box features like Nextra’s Markdown-powered documentation setup might not benefit from SSR, as they are intended to be pre-rendered at build time.
# When to Use Server-Side Programming in Nextra
- Authenticated content: For example, to check user authentication status and render content conditionally.
- Dynamic content: When data needs to be fetched fresh on each request (e.g., real-time data, personalized content).
- APIs: To create backend functionality without needing a separate server.
In general, Nextra’s server-side capabilities make it more flexible, but it’s best suited for projects where most pages remain static.