From Development to Deployment: Next.js Full-Stack Best Practices on EdgeOne Pages

Ethan MercerEthan Mercer
10 min read
Spt 8, 2025

This guide focuses on building high-performance Next.js full-stack applications and optimizing your development workflow without compromising code quality or application speed.

In the upcoming sections, we'll thoroughly examine project architecture, environment configuration, routing strategies, and efficient data fetching techniques. These best practices will enable you to deploy Next.js full-stack applications seamlessly on EdgeOne Pages, harnessing edge computing power for superior performance and worldwide accessibility.

Directory Structure Recommendations

The following directory structure represents our recommended Next.js hybrid rendering template. This carefully organized architecture serves as an optimal foundation for developers building scalable full-stack applications:

next-mix-template/
├── src/                   
│   ├── app/               # Next.js App Router
│   │   ├── layout.tsx     
│   │   ├── page.tsx       # Home page component
│   │   ├── ssr/           # SSR demonstration pages
│   │   ├── isr/           # ISR demonstration pages
│   │   ├── ssg/           # SSG demonstration pages
│   │   ├── streaming/     # Streaming demonstration pages
│   │   ├── node-functions/# Node Functions demonstration pages
│   │   ├── edge-functions/# Edge Functions demonstration pages
│   │   ├── api/           # API routes
│   │   └── globals.css    
│   ├── components/        # React components
│   │   ├── ui/           
│   │   ├── Header.tsx    
│   │   ├── Hero.tsx      
│   │   ├── Features.tsx  
│   │   └── FeatureCard.tsx 
│   └── lib/              # Utility functions
├── public/               # Static resources
├── package.json          # Project configuration
├── next.config.ts        # Next.js configuration
├── tailwind.config.ts    
├── tsconfig.json         
└── components.json       # shadcn/ui configuration

This project follows the standard Next.js App Router architecture with two specialized backend function directories that integrate seamlessly with EdgeOne Pages:

The edge-functions directory serves as the entry point for EdgeOne Pages' edge computing capabilities. Place lightweight requests here to benefit from rapid response times as they execute directly from edge nodes closest to your users, delivering exceptional speed and performance.

EdgeOne Pages provides node-functions, a centralized function deployment environment where you can leverage the extensive Node.js package ecosystem. This enables you to quickly implement more complex functionality requiring full Node.js capabilities while maintaining a streamlined development experience.

For more comprehensive details about edge-functions and node-functions, please refer to the official documentation.

Routes and API Integration

EdgeOne Pages enables direct access to pages and API endpoints via domain-based path routing. The mapping follows a consistent pattern where file locations directly correspond to URL paths - for instance, /app/node-functions/get-users/index.js becomes accessible at {domain}/get-users.

This platform features an intuitive routing system with automatic path mapping:

Key Features:

  • Automatic Path Mapping: File system structure directly translates to URL routes
  • Zero Configuration: No manual routing setup required
  • Unified Route Management: Seamless handling of both page and API routes
  • Port-Free Access: Direct domain-based routing eliminates port specifications
Project structure:
/app/node-functions/get-users/index.js
/app/node-functions/create-user/index.js
/app/node-functions/products/[id].js

/app/edge-functions/get-products/index.js (edge)

Access method:
https://yourdomain.com/get-users      → run get-users/index.js
https://yourdomain.com/create-user    → run create-user/index.js
https://yourdomain.com/products/123   → run products/[id].js
https://yourdomain.com/get-products   → run get-products/index.js (edge)

Page Rendering & Data Fetching

Next.js offers multiple rendering strategies, each with distinct data fetching mechanisms. EdgeOne Pages provides comprehensive support for Next.js's three primary rendering modes - Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR) - enabling developers to leverage all approaches within the same application.

1. App Router Data Fetching (Next.js 13+)

The new App Router employs different approaches for data fetching. For comprehensive details, consult the official Next.js documentation.

// app/products/page.js
// Default static rendering
async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    cache: 'force-cache' // Default Cache
  })
  return res.json()
}

export default async function ProductsPage() {
  const products = await getProducts()
  
  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

Setting revalidate: 0 forces pages to use server-side rendering for data retrieval on every request.

// app/live/page.js
// Dynamic rendering (similar to getServerSideProps)
async function getLiveData() {
  const res = await fetch('https://api.example.com/live', {
    cache: 'no-store', // No caching, get new data for each request
    // 或使用
    next: { revalidate: 0 }
  })
  return res.json()
}

// ISR in App Router
async function getDataWithISR() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 60 } // Re-verify after 60 seconds
  })
  return res.json()
}
// pages/dashboard.js
// Combining static and client-side data fetching
export async function getStaticProps() {
  // Get data that doesn't change often
  const staticData = await fetch('https://api.example.com/config')
  const config = await staticData.json()

  return {
    props: { config },
    revalidate: 3600, // 1 hour
  }
}

function Dashboard({ config }) {
  // Client obtains real-time data
  const { data: liveData } = useSWR('/api/live-stats', fetcher, {
    refreshInterval: 5000
  })

  return (
    <div>
      <h1>{config.title}</h1>
      {liveData && <div>online users: {liveData.onlineUsers}</div>}
    </div>
  )
}

2. getStaticProps (Static Generation)

Fetches data during the build process and generates static HTML pages. Ideal for content that remains relatively stable and doesn't require frequent updates.

// pages/products.js
export async function getStaticProps() {
  const res = await fetch('https://api.example.com/products')
  const products = await res.json()

  return {
    props: {
      products,
    }
  }
}

export default function Products({ products }) {
  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

Use Case: Next.js Official Website (nextjs.org)

3. getServerSideProps (Server-Side Rendering)

Server-Side Rendering (SSR) retrieves data server-side on every request. This method is ideal for pages requiring real-time data updates or access to request-specific information.

// pages/user/[id].js
export async function getServerSideProps(context) {
  const { id } = context.params
  const { req, res, query } = context
  
  const cookies = context.req.cookies
  
  const userData = await fetch(`https://api.example.com/users/${id}`)
  const user = await userData.json()

  if (!user) {
    return {
      notFound: true, 
    }
  }

  return {
    props: {
      user,
    }
  }
}

export default function User({ user }) {
  return <div>Welcome, {user.name}!</div>
}

Use Case: LinkedIn (linkedin.com)

4. ISR (Incremental Static Regeneration)

The revalidate parameter enables Incremental Static Regeneration (ISR), providing rule-based, on-demand page data updates.

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/blog')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Three ISR strategies
    revalidate: 60, // Regenerate after a time interval in seconds
    // or
    revalidate: false, // Disable ISR and only generate it at build time
    // or
    revalidate: true, // On-demand ISR (configuration required)
  }
}

Use Case: Dev.to (dev.to). Popular content updates every 5 minutes

Method Comparison and Decision Matrix

MethodExecution TimeUse CaseSEOPerformance
getStaticPropsBuild timeStatic content✅ Excellent⚡ Fastest
getServerSidePropsEvery requestDynamic content/Personalization✅ Good🐢 Slower
ISR (revalidate)Build time + Periodic updatesSemi-static content✅ Good⚡ Fast
App Router fetchDepends on configurationFlexible✅ Good🔧 Configurable

Developers use a decision matrix:

Website TypeRecommended StrategyRepresentative SitesKey Considerations
Documentation/BlogSSGNext.js Docs, MDNSEO, Performance, Cost
E-commerce PlatformSSR/ISRAmazon, ShopifyReal-time Inventory, Personalization
Social MediaSSRTwitter, LinkedInReal-time Updates, Personalization
News MediaISR/SSRCNN, BBCTimeliness, SEO
SaaS Marketing SiteSSG/ISRStripe, SlackPerformance, Conversion Rate
Corporate WebsiteSSGApple, MicrosoftBrand Presentation, Performance
Online EducationISRCoursera, UdemyContent Updates, SEO
Streaming MediaSSRNetflix, SpotifyPersonalization, Real-time Recommendations

Environment Variables Management

EdgeOne Pages adheres to Next.js conventions for environment variable management:

  • Public Variables : Environment variables prefixed with `NEXT_PUBLIC_` will be embedded in the client-side bundle and accessible in browser code. These variables should only contain non-sensitive information.
  • Protected Variables: For sensitive information such as API keys, authentication tokens (e.g., `AI_TOKEN`), or other credentials, omit the `NEXT_PUBLIC_` prefix. These variables will remain server-side only, protecting them from exposure in the client-side code.

This consistent approach allows you to manage visibility and security of configuration values while maintaining compatibility with the standard Next.js environment variable system.

# private env

OPENAI_API_URL=
OPENAI_API_KEY=

DB_HOST=
DB_PORT=
DB_USER=
DB_PASSWORD=
DB_NAME=

# public env

NEXT_PUBLIC_AUTHOR=
NEXT_PUBLIC_EMAIL=

Usually, a single .env.local file is sufficient. However, sometimes you may want to add some default settings separately for the development (next dev) or production (next start) environments.

Next.js allows you to set default values in .env (for all environments), .env.development (for development environment), and .env.production (for production environment)..env.local always overrides the default settings.

When deploying to EdgeOne Pages, you'll need to configure environment variables in the EdgeOne console that match those in your local .env files. EdgeOne Pages follows Next.js conventions where variables prefixed with NEXT_PUBLIC_ are embedded in client-side code, while variables without this prefix remain protected server-side. 

Note: For security, always add .env* files to your .gitignore to prevent sensitive credentials like API keys and tokens from being accidentally committed to your repository.

Conclude

EdgeOne Pages provides comprehensive native support for Next.js full-stack projects, perfectly compatible with various core Next.js features including Static Site Generation (SSG), Server-Side Rendering (SSR), Incremental Static Regeneration (ISR), and API Routes. Developers can leverage all the advantages of the Next.js framework without complex configuration adjustments, enabling flexible rendering strategy selection and an efficient full-stack development experience.

Thanks to EdgeOne Pages' streamlined deployment process, development teams can quickly deploy Next.js applications to the global edge network through EdgeOne CLI operations or CI/CD integration. This seamless deployment experience not only significantly reduces operational complexity but also delivers exceptional performance and reliability through edge computing capabilities, allowing developers to focus more on implementing business logic without worrying about infrastructure management and optimization.