├── .eslintrc.json ├── .gitignore ├── .vercelignore ├── README.md ├── app ├── blog │ └── [slug] │ │ └── page.tsx ├── components │ ├── ModeToggle.tsx │ ├── Navbar.tsx │ └── theme-provider.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── lib │ ├── interface.ts │ └── sanity.ts └── page.tsx ├── components.json ├── components └── ui │ ├── button.tsx │ ├── card.tsx │ └── dropdown-menu.tsx ├── lib └── utils.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg ├── nextjs-14-logo.png ├── nuxt-js-logo.jpeg ├── remix-run-logo.jpg ├── svelete-kit-logo.jpg └── vercel.svg ├── sanity ├── .eslintrc ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── sanity.cli.ts ├── sanity.config.ts ├── schemas │ ├── blog.ts │ └── index.ts ├── static │ └── .gitkeep └── tsconfig.json ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | sanity -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Please check public folder for images 2 | 3 | # Blog 1: 4 | small Description: Explore the groundbreaking features and enhancements that come with Next.js 14, the latest version of the popular React framework, and discover how it's set to revolutionize the way we build web applications. 5 | 6 | ### Unveiling Next.js 14: A Quantum Leap in Web Development 7 | In the ever-evolving landscape of web development, staying at the forefront of technological advancements is crucial. Enter Next.js 14, the latest iteration of the widely acclaimed React framework. Packed with cutting-edge features and improvements, Next.js 14 is poised to redefine the way developers approach building web applications. Let's delve into the key highlights and game-changing aspects that make this version a must-explore for every web developer. 8 | 9 | #### 1. Incremental Static Regeneration (ISR): A Performance Boost Like Never Before 10 | Next.js 14 introduces Incremental Static Regeneration, a revolutionary feature that takes the concept of static site generation to new heights. ISR allows developers to update static pages without rebuilding the entire site, significantly improving performance and reducing the time it takes to deploy changes. This game-changing capability ensures that your web applications remain fast and responsive, providing an enhanced user experience. 11 | 12 | #### 2. Faster Builds with Builder Improvements 13 | Building on the foundation of its predecessor, Next.js 14 comes with substantial builder improvements. The build times have been optimized, ensuring faster development workflows. Developers will appreciate the efficiency gains, allowing them to iterate more rapidly and bring their ideas to life with minimal wait times. 14 | 15 | #### 3. Easier API Routes with API Clustering 16 | API routes are a core part of Next.js, and version 14 makes working with them even more straightforward. The introduction of API clustering streamlines the process of managing and organizing API routes, making it easier for developers to structure their applications and maintain clean and maintainable code. 17 | 18 | 19 | #### 4. Improved Image Component for Optimal Performance 20 | Images play a crucial role in web development, and Next.js 14 acknowledges this with an enhanced image component. With built-in support for modern image formats like AVIF and WebP, developers can ensure optimal image performance, improving page load times and overall user experience. 21 | 22 | #### 5. Zero Config Dynamic Imports: Simplifying Code Splitting 23 | Code splitting is a key optimization technique, and Next.js 14 simplifies it further with zero config dynamic imports. Developers can seamlessly split their code without the need for complex configurations, resulting in more maintainable and efficient codebases. 24 | 25 | 26 | #### Conclusion: Embracing the Future of Web Development with Next.js 14 27 | As we embrace the era of Next.js 14, it's evident that the framework continues to be a trailblazer in the world of web development. The combination of Incremental Static Regeneration, faster builds, API clustering, improved image handling, and simplified code splitting positions Next.js 14 as a powerful tool for developers looking to create performant, scalable, and modern web applications. 28 | 29 | Whether you're a seasoned developer or just starting your journey, exploring the capabilities of Next.js 14 is a worthwhile endeavor. Stay ahead of the curve, leverage the latest features, and unlock new possibilities in web development with Next.js 14. The future of the web is here, and it's built with Next.js. 30 | 31 | 32 | # Blog 2 33 | Small Description: Discover the innovative features and paradigm-shifting capabilities of Remix.run, the modern web framework that's redefining how developers build robust and scalable applications. 34 | 35 | ### Unleashing the Power of Remix.run: A New Dawn in Web Development 36 | In the dynamic landscape of web development, Remix.run stands out as a beacon of innovation, challenging traditional approaches and setting a new standard for building web applications. This article takes you on a journey into the world of Remix.run, exploring its unique features and demonstrating why it's gaining momentum as the go-to framework for the next generation of web development. 37 | 38 | #### 1. The Remix Architecture: A Breath of Fresh Air 39 | Remix.run introduces a fresh and intuitive architecture that aligns seamlessly with modern development practices. With a focus on routes, data loading, and components, Remix simplifies complex workflows, providing developers with a clear and efficient structure for building scalable applications. Say goodbye to boilerplate and hello to a more intuitive development experience. 40 | 41 | #### 2. Enhanced Data Loading with Loader Functions 42 | Data loading is a critical aspect of web development, and Remix.run takes it to the next level with Loader Functions. These functions allow developers to fetch data for a route, ensuring that the right information is available when rendering components. The result is a more streamlined and predictable data-loading process, empowering developers to create performant applications with ease. 43 | 44 | #### 3. Server-Side Rendering (SSR) for Optimal Performance 45 | Remix.run is designed with performance in mind, and server-side rendering (SSR) is a key feature that contributes to faster page loads and improved SEO. By rendering pages on the server and sending fully-formed HTML to the client, Remix.run ensures a smoother user experience and better search engine visibility. 46 | 47 | #### 4. Built-In Authentication: A Developer's Dream 48 | Authentication is often a complex aspect of web development, but Remix.run simplifies it with built-in support for authentication. Developers can easily implement authentication strategies, secure their applications, and focus on building features without getting bogged down by intricate authentication setups. 49 | 50 | #### 5. Dynamic Client-Side Navigation with Client-Only Routes 51 | Remix.run embraces the flexibility of client-side navigation with Client-Only Routes. This feature allows developers to handle specific routes entirely on the client side, providing dynamic and seamless navigation experiences. It's a powerful tool for building modern, interactive web applications. 52 | 53 | #### Conclusion: Embracing the Future with Remix.run 54 | As we navigate the ever-evolving landscape of web development, Remix.run emerges as a guiding force, offering a paradigm shift in how we approach building applications. With its intuitive architecture, enhanced data loading, server-side rendering, built-in authentication, and dynamic client-side navigation, Remix.run empowers developers to create cutting-edge, performant, and scalable web applications. 55 | 56 | Whether you're a seasoned developer looking for a modern framework or a newcomer eager to explore the latest in web development, Remix.run is worth your attention. Step into the future of web development with Remix.run and experience a new era of creativity, efficiency, and innovation. The journey begins here. 57 | 58 | 59 | # Blog 3 60 | Small Description: Dive into the world of Nuxt.js, the powerful Vue.js framework that simplifies and enhances the process of building robust and performant web applications. Explore its key features and discover how Nuxt.js is reshaping the landscape of Vue development. 61 | 62 | ### Nuxt.js Unveiled: Elevating Vue.js Development to New Heights 63 | In the realm of Vue.js development, Nuxt.js has emerged as a game-changer, offering developers a powerful and streamlined framework for building modern web applications. This article is your passport to understanding the magic of Nuxt.js, delving into its features and showcasing why it has become the go-to choice for Vue.js enthusiasts worldwide. 64 | 65 | #### 1. Universal Application with Server-Side Rendering (SSR) 66 | At the heart of Nuxt.js lies the concept of universal applications, made possible through Server-Side Rendering (SSR). This feature ensures that your Vue.js applications are not only dynamic on the client side but also rendered on the server, enhancing performance, SEO, and the overall user experience. 67 | 68 | #### 2. Automatic Code Splitting for Optimized Loading 69 | Efficiency is paramount in web development, and Nuxt.js excels in this aspect with automatic code splitting. By breaking down your application into smaller chunks, Nuxt.js ensures that only the necessary code is loaded, resulting in faster page loads and a more responsive user interface. 70 | 71 | #### 3. Effortless Routing with Convention over Configuration 72 | Say goodbye to complex routing configurations. Nuxt.js adopts a convention over configuration approach, making routing a breeze. With file-based routing, developers can focus on building features rather than spending time configuring routes, leading to a more enjoyable and productive development experience. 73 | 74 | #### 4. Seamless Integration of Middleware 75 | Nuxt.js introduces middleware, a powerful tool that allows developers to define functions to be executed before rendering pages or layouts. This feature enables seamless integration of additional functionalities, such as authentication checks or data fetching, enhancing the flexibility of your Vue.js applications. 76 | 77 | #### 5. Tailored Vue.js Configurations with Nuxt Modules 78 | Customization is key, and Nuxt Modules provide a structured way to extend and configure your Vue.js applications. Whether you're integrating a third-party library or adding a specific feature, Nuxt Modules simplify the process, ensuring that your customizations are organized and maintainable. 79 | 80 | #### Conclusion: Elevate Your Vue.js Development with Nuxt.js 81 | In the dynamic world of web development, Nuxt.js has established itself as a powerhouse for building Vue.js applications. With its focus on universal applications, automatic code splitting, intuitive routing, middleware support, and modular configurations, Nuxt.js offers a robust framework that caters to both beginners and seasoned developers. 82 | 83 | As you embark on your Vue.js journey or seek to enhance your existing projects, consider Nuxt.js as your ally. Explore the possibilities, embrace the simplicity, and elevate your Vue.js development with the versatile and feature-rich framework that is Nuxt.js. The future of Vue.js development starts here. 84 | 85 | 86 | 87 | # Blog 4 88 | Small Description: Explore the innovative features of SvelteKit, the modern framework built on the principles of Svelte, designed to simplify and streamline the process of creating dynamic and performant web applications. 89 | 90 | ### SvelteKit Unleashed: Transforming Web Development with Ease 91 | In the dynamic landscape of web development, SvelteKit stands out as a breath of fresh air, offering a streamlined approach to building dynamic and performant web applications. This article is your guide to understanding the revolutionary features of SvelteKit and how it's reshaping the way we approach frontend development. 92 | 93 | #### 1. The Power of Svelte: A Foundation for Efficiency 94 | Built on the principles of Svelte, SvelteKit inherits the efficiency and elegance of the original framework. With a focus on compiling components at build time and generating highly optimized JavaScript, SvelteKit ensures minimal overhead and maximal performance in your web applications. 95 | 96 | #### 2. File-Based Routing for Intuitive Project Structure 97 | Say goodbye to complex routing configurations. SvelteKit adopts a file-based routing system, making it intuitive and straightforward to organize your project structure. Each file in the src/routes directory corresponds to a route in your application, simplifying navigation and enhancing code maintainability. 98 | 99 | #### 3. Serverless Functions for Seamless Backend Integration 100 | SvelteKit embraces the serverless architecture with built-in support for serverless functions. These functions allow developers to handle backend logic seamlessly, enabling smooth integration with databases, APIs, and other server-side functionalities. The result is a cohesive development experience that bridges the gap between frontend and backend seamlessly. 101 | 102 | #### 4. Stores and Actions: State Management Made Simple 103 | SvelteKit introduces the concept of stores and actions for state management. With a clear and concise syntax, developers can manage application state effortlessly. SvelteKit's approach to state management eliminates the need for complex libraries, providing a lightweight and efficient solution for building reactive and interactive web applications. 104 | 105 | #### 5. Optimized Loading with Built-In Routing Transitions 106 | Enhance the user experience with SvelteKit's built-in routing transitions. These transitions enable smooth page transitions and loading animations, creating a polished and engaging feel to your web applications. SvelteKit takes care of the heavy lifting, allowing developers to focus on creating delightful user interfaces. 107 | 108 | #### Conclusion: Elevate Your Web Development with SvelteKit 109 | As we navigate the evolving landscape of web development, SvelteKit emerges as a powerful and efficient framework. Whether you're a frontend enthusiast or a seasoned developer, exploring the capabilities of SvelteKit is a journey worth taking. With its focus on efficiency, intuitive project structure, serverless functions, streamlined state management, and optimized loading, SvelteKit is set to redefine the way we build dynamic web applications. 110 | 111 | Step into the future of frontend development with SvelteKit. Embrace simplicity, enhance performance, and elevate your web development experience. The revolution is here, and it's called SvelteKit. 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 127 | 128 | ## Getting Started 129 | 130 | First, run the development server: 131 | 132 | ```bash 133 | npm run dev 134 | # or 135 | yarn dev 136 | # or 137 | pnpm dev 138 | # or 139 | bun dev 140 | ``` 141 | 142 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 143 | 144 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 145 | 146 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 147 | 148 | ## Learn More 149 | 150 | To learn more about Next.js, take a look at the following resources: 151 | 152 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 153 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 154 | 155 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 156 | 157 | ## Deploy on Vercel 158 | 159 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 160 | 161 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 162 | -------------------------------------------------------------------------------- /app/blog/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { fullBlog } from "@/app/lib/interface"; 2 | import { client, urlFor } from "@/app/lib/sanity"; 3 | import { PortableText } from "@portabletext/react"; 4 | import Image from "next/image"; 5 | 6 | export const revalidate = 30; // revalidate at most 30 seconds 7 | 8 | async function getData(slug: string) { 9 | const query = ` 10 | *[_type == "blog" && slug.current == '${slug}'] { 11 | "currentSlug": slug.current, 12 | title, 13 | content, 14 | titleImage 15 | }[0]`; 16 | 17 | const data = await client.fetch(query); 18 | return data; 19 | } 20 | 21 | export default async function BlogArticle({ 22 | params, 23 | }: { 24 | params: { slug: string }; 25 | }) { 26 | const data: fullBlog = await getData(params.slug); 27 | 28 | return ( 29 |
30 |

31 | 32 | Jan Marshal - Blog 33 | 34 | 35 | {data.title} 36 | 37 |

38 | 39 | Title Image 47 | 48 |
49 | 50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /app/components/ModeToggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Moon, Sun } from "lucide-react"; 5 | import { useTheme } from "next-themes"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from "@/components/ui/dropdown-menu"; 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /app/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ModeToggle } from "./ModeToggle"; 3 | 4 | export default function Navbar() { 5 | return ( 6 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /app/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ski043/nextjs14-blog-tutorial/22f2d76aa8f8cdeb31b06fb50250509ec7371cf5/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 222.2 84% 4.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 222.2 84% 4.9%; 13 | --primary: 221.2 83.2% 53.3%; 14 | --primary-foreground: 210 40% 98%; 15 | --secondary: 210 40% 96.1%; 16 | --secondary-foreground: 222.2 47.4% 11.2%; 17 | --muted: 210 40% 96.1%; 18 | --muted-foreground: 215.4 16.3% 46.9%; 19 | --accent: 210 40% 96.1%; 20 | --accent-foreground: 222.2 47.4% 11.2%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 210 40% 98%; 23 | --border: 214.3 31.8% 91.4%; 24 | --input: 214.3 31.8% 91.4%; 25 | --ring: 221.2 83.2% 53.3%; 26 | --radius: 0.5rem; 27 | } 28 | 29 | .dark { 30 | --background: 222.2 84% 4.9%; 31 | --foreground: 210 40% 98%; 32 | --card: 222.2 84% 4.9%; 33 | --card-foreground: 210 40% 98%; 34 | --popover: 222.2 84% 4.9%; 35 | --popover-foreground: 210 40% 98%; 36 | --primary: 217.2 91.2% 59.8%; 37 | --primary-foreground: 222.2 47.4% 11.2%; 38 | --secondary: 217.2 32.6% 17.5%; 39 | --secondary-foreground: 210 40% 98%; 40 | --muted: 217.2 32.6% 17.5%; 41 | --muted-foreground: 215 20.2% 65.1%; 42 | --accent: 217.2 32.6% 17.5%; 43 | --accent-foreground: 210 40% 98%; 44 | --destructive: 0 62.8% 30.6%; 45 | --destructive-foreground: 210 40% 98%; 46 | --border: 217.2 32.6% 17.5%; 47 | --input: 217.2 32.6% 17.5%; 48 | --ring: 224.3 76.3% 48%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply bg-background text-foreground; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | import { ThemeProvider } from "./components/theme-provider"; 5 | import Navbar from "./components/Navbar"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata: Metadata = { 10 | title: "Create Next App", 11 | description: "Generated by create next app", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 28 | 29 |
{children}
30 |
31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/lib/interface.ts: -------------------------------------------------------------------------------- 1 | export interface simpleBlogCard { 2 | title: string; 3 | smallDescription: string; 4 | currentSlug: string; 5 | titleImage: any; 6 | } 7 | 8 | export interface fullBlog { 9 | currentSlug: string; 10 | title: string; 11 | content: any; 12 | titleImage: any; 13 | } 14 | -------------------------------------------------------------------------------- /app/lib/sanity.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "next-sanity"; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | export const client = createClient({ 5 | apiVersion: "2023-05-03", 6 | dataset: "production", 7 | projectId: "sw0fgp2s", 8 | useCdn: false, 9 | }); 10 | 11 | const builder = imageUrlBuilder(client); 12 | 13 | export function urlFor(source: any) { 14 | return builder.image(source); 15 | } 16 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent } from "@/components/ui/card"; 2 | import { simpleBlogCard } from "./lib/interface"; 3 | import { client, urlFor } from "./lib/sanity"; 4 | import Image from "next/image"; 5 | import { Button } from "@/components/ui/button"; 6 | import Link from "next/link"; 7 | 8 | export const revalidate = 30; // revalidate at most 30 seconds 9 | 10 | async function getData() { 11 | const query = ` 12 | *[_type == 'blog'] | order(_createdAt desc) { 13 | title, 14 | smallDescription, 15 | "currentSlug": slug.current, 16 | titleImage 17 | }`; 18 | 19 | const data = await client.fetch(query); 20 | 21 | return data; 22 | } 23 | 24 | export default async function Home() { 25 | const data: simpleBlogCard[] = await getData(); 26 | 27 | console.log(data); 28 | 29 | return ( 30 |
31 | {data.map((post, idx) => ( 32 | 33 | image 40 | 41 | 42 |

{post.title}

43 |

44 | {post.smallDescription} 45 |

46 | 49 |
50 |
51 | ))} 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: "https", 7 | hostname: "cdn.sanity.io", 8 | port: "", 9 | }, 10 | ], 11 | }, 12 | }; 13 | 14 | module.exports = nextConfig; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@portabletext/react": "^3.0.11", 13 | "@radix-ui/react-dropdown-menu": "^2.0.6", 14 | "@radix-ui/react-slot": "^1.0.2", 15 | "@sanity/image-url": "^1.0.2", 16 | "class-variance-authority": "^0.7.0", 17 | "clsx": "^2.0.0", 18 | "lucide-react": "^0.298.0", 19 | "next": "14.0.4", 20 | "next-sanity": "^7.0.4", 21 | "next-themes": "^0.2.1", 22 | "react": "^18", 23 | "react-dom": "^18", 24 | "tailwind-merge": "^2.1.0", 25 | "tailwindcss-animate": "^1.0.7" 26 | }, 27 | "devDependencies": { 28 | "@tailwindcss/typography": "^0.5.10", 29 | "@types/node": "^20", 30 | "@types/react": "^18", 31 | "@types/react-dom": "^18", 32 | "autoprefixer": "^10.0.1", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.0.4", 35 | "postcss": "^8", 36 | "tailwindcss": "^3.3.0", 37 | "typescript": "^5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/nextjs-14-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ski043/nextjs14-blog-tutorial/22f2d76aa8f8cdeb31b06fb50250509ec7371cf5/public/nextjs-14-logo.png -------------------------------------------------------------------------------- /public/nuxt-js-logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ski043/nextjs14-blog-tutorial/22f2d76aa8f8cdeb31b06fb50250509ec7371cf5/public/nuxt-js-logo.jpeg -------------------------------------------------------------------------------- /public/remix-run-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ski043/nextjs14-blog-tutorial/22f2d76aa8f8cdeb31b06fb50250509ec7371cf5/public/remix-run-logo.jpg -------------------------------------------------------------------------------- /public/svelete-kit-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ski043/nextjs14-blog-tutorial/22f2d76aa8f8cdeb31b06fb50250509ec7371cf5/public/svelete-kit-logo.jpg -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sanity/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/eslint-config-studio" 3 | } 4 | -------------------------------------------------------------------------------- /sanity/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # Compiled Sanity Studio 9 | /dist 10 | 11 | # Temporary Sanity runtime, generated by the CLI on every dev server start 12 | /.sanity 13 | 14 | # Logs 15 | /logs 16 | *.log 17 | 18 | # Coverage directory used by testing tools 19 | /coverage 20 | 21 | # Misc 22 | .DS_Store 23 | *.pem 24 | 25 | # Typescript 26 | *.tsbuildinfo 27 | 28 | # Dotenv and similar local-only files 29 | *.local 30 | -------------------------------------------------------------------------------- /sanity/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Clean Content Studio 2 | 3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend. 4 | 5 | Now you can do the following things: 6 | 7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) 8 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 9 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 10 | -------------------------------------------------------------------------------- /sanity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-14-blog", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "package.json", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "dev": "sanity dev", 9 | "start": "sanity start", 10 | "build": "sanity build", 11 | "deploy": "sanity deploy", 12 | "deploy-graphql": "sanity graphql deploy" 13 | }, 14 | "keywords": [ 15 | "sanity" 16 | ], 17 | "dependencies": { 18 | "@sanity/vision": "^3.21.3", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-is": "^18.2.0", 22 | "sanity": "^3.21.3", 23 | "styled-components": "^6.0.7" 24 | }, 25 | "devDependencies": { 26 | "@sanity/eslint-config-studio": "^3.0.1", 27 | "@types/react": "^18.0.25", 28 | "eslint": "^8.6.0", 29 | "prettier": "^3.0.2", 30 | "typescript": "^5.1.6" 31 | }, 32 | "prettier": { 33 | "semi": false, 34 | "printWidth": 100, 35 | "bracketSpacing": false, 36 | "singleQuote": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sanity/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import {defineCliConfig} from 'sanity/cli' 2 | 3 | export default defineCliConfig({ 4 | api: { 5 | projectId: 'sw0fgp2s', 6 | dataset: 'production' 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /sanity/sanity.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'sanity' 2 | import {deskTool} from 'sanity/desk' 3 | import {visionTool} from '@sanity/vision' 4 | import {schemaTypes} from './schemas' 5 | 6 | export default defineConfig({ 7 | name: 'default', 8 | title: 'nextjs-14-blog', 9 | 10 | projectId: 'sw0fgp2s', 11 | dataset: 'production', 12 | 13 | plugins: [deskTool(), visionTool()], 14 | 15 | schema: { 16 | types: schemaTypes, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /sanity/schemas/blog.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'blog', 3 | type: 'document', 4 | title: 'Blog', 5 | fields: [ 6 | { 7 | name: 'title', 8 | type: 'string', 9 | title: 'Title of blog article', 10 | }, 11 | { 12 | name: 'slug', 13 | type: 'slug', 14 | title: 'Slug of your blog article', 15 | options: { 16 | source: 'title', 17 | }, 18 | }, 19 | { 20 | name: 'titleImage', 21 | type: 'image', 22 | title: 'Title Image', 23 | }, 24 | { 25 | name: 'smallDescription', 26 | type: 'text', 27 | title: 'Small Description', 28 | }, 29 | { 30 | name: 'content', 31 | type: 'array', 32 | title: 'Content', 33 | of: [ 34 | { 35 | type: 'block', 36 | }, 37 | ], 38 | }, 39 | ], 40 | } 41 | -------------------------------------------------------------------------------- /sanity/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import blog from './blog' 2 | 3 | export const schemaTypes = [blog] 4 | -------------------------------------------------------------------------------- /sanity/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /sanity/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | "./pages/**/*.{ts,tsx}", 6 | "./components/**/*.{ts,tsx}", 7 | "./app/**/*.{ts,tsx}", 8 | "./src/**/*.{ts,tsx}", 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], 76 | }; 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------