- Key Features
- Shared Studio
- Getting Started
- Scripts
- Project Structure
- Custom Desk Structure
- Managing Content
- Development
- Key Dependencies
- Documentation and Resources
- Credits
- CMS-Driven Content Management: Pages are dynamically constructed using content blocks defined in Sanity. This CMS-like approach enables content creators to manage all aspects of the site, from page content to menus and SEO, without touching the codebase.
- Storybook Integration: Storybook enables isolated component development and testing. This allows for the visual verification of UI components, ensuring design consistency and usability across the application.
- Scoped Styling with CSS Modules: CSS Modules are used for local scoping of styles, preventing naming conflicts and making it easier to maintain and scale the styling of components.
- Universal Content Control: Manage everything from a single location in Sanity—menus, SEO settings, logos and pages—offering a unified content management experience.
- Custom Theming and Typography: Global styles such as colors are managed in global.css, while typography is primarily controlled within Text.tsx, ensuring a consistent design language throughout the application.
In addition to the primary Sanity Studio (accessible at http://localhost:3000/studio), this project includes a secondary studio called Shared Studio. This studio is designed to manage shared content between the different domains .no and .se, such as blog posts and customer cases.
- Customer Cases: Customer Cases is another content type available in the Shared Studio, enabling you to showcase client stories or testimonials.
Accessing Shared Studio
- Studio URL: The Shared Studio is accessible at http://localhost:3000/shared.
- Frontend Access: Content from the Shared Studio can be accessed on the frontend through API calls using sharedClient or loadSharedQuery.
To get started, follow these steps:
-
Clone the Repository
-
Install Dependencies:
npm install
-
Set Up Environment Variables: Ensure that the following environment variables are configured in your .env.local file:
# ENV KEYS FOR UNIQUE STUDIO NEXT_PUBLIC_SANITY_PROJECT_ID=<Your Sanity Project ID> NEXT_PUBLIC_SANITY_DATASET=<Your Sanity Dataset> NEXT_PUBLIC_SANITY_API_VERSION=<Your Sanity API Version> SANITY_API_TOKEN_DEV=<Your Sanity API Developer Token> SANITY_API_TOKEN_PROD=<Your Sanity API Viewer Token> # ENV KEYS FOR SHARED STUDIO NEXT_PUBLIC_SANITY_SHARED_PROJECT_ID=<Your Sanity SHARED Project ID> NEXT_PUBLIC_SANITY_SHARED_DATASET=<Your Sanity SHARED Dataset> NEXT_PUBLIC_SANITY_SHARED_API_VERSION=<Your Sanity SHARED API Version> SANITY_SHARED_API_TOKEN_DEV=<Your Sanity SHARED API Developer Token> SANITY_SHARED_API_TOKEN_PROD=<Your Sanity SHARED API Viewer Token>
-
Configure Sanity: You will need access to a Sanity project. Set up a new project or gain access to an existing one. Ensure you have the necessary API tokens and credentials.
-
Start the Development Server:
npm run dev
-
View the Application: Open your browser and go to http://localhost:3000 to see the application.
npm run dev
: Start the Next.js development server.npm run build
: Build the Next.js application for production.npm run start
: Start the Next.js application in production mode.npm run format
: Format the codebase using Prettier to ensure consistent code style across the project.npm run lint
: Lint the project using ESLint.npm run fix
: Format and lint in one command.
The project follows a component-based architecture with a focus on modularity and reusability. Below is an overview of the main directories:
- src/app: Contains the main application routes and layout components, organizing the site’s pages and their respective content.
- src/api: API routes are handled here, including form submissions and any other backend processes.
- src/components: Houses reusable UI components, such as buttons, forms, navigation elements, and content sections. These components are the building blocks for pages.
- src/lib: Utility functions, data-fetching logic, and other shared code that supports the application’s functionality.
- src/schemas: Sanity content models (schemas) defining the structure of the content stored in the CMS.
- src/stories: Contains Storybook stories for the UI components, used for visual testing and component documentation.
This project includes a customized Sanity Studio desk structure to enhance content management, focusing especially on Navigation Management and Social Media Profiles.
The Company Information
menu allows you to configure global settings for your site, including brand assets, tracking codes, and default SEO settings.
- Adding Social Media Links: Editors can manage social media links under the
Social Media Profiles
menu. This allows visitors to connect with the website on various social platforms. - Supported Platforms: The 9 supported platforms include Facebook, Instagram, and LinkedIn, but more can be added to
SoMePlatforms
if needed.
- Setting the Landing Page: The
Navigation Manager
allows editors to set the landing page for the site, which is crucial for determining the primary page visitors see upon arrival. - Adding Menu Items: Within the
Navigation Manager
, editors can add items to various pre-defined menus:- Main Menu: Add links and a single Call to Action (if needed) to the main menu, which appears at the top of the website. This helps visitors navigate to important sections.
- Footer Menu: Add items to the footer menu, which consists of different sections. Each section can contain either social media links, custom links, text, or images (e.g., logos).
- Sidebar Menu: Add links to the sidebar menu, which will appear on smaller screens to aid mobile navigation.
- Creating Pages: Content editors can create and manage pages under the
Pages
menu in the Sanity Studio. - Adding Sections: Each page can be customized with structured content that includes various predefined sections such as hero, article, testimonials, features, callToAction, grid, and callout.
When building custom components in Sanity Studio that need to fetch data, it's important to use the fetchWithToken
utility to ensure that your API tokens remain secure.
Why Use fetchWithToken
?
fetchWithToken
allows you to securely fetch data from Sanity without exposing sensitive API tokens in client-side code. This is crucial when developing custom input components or other features within Sanity Studio that need to interact with Sanity's content APIs.
Implementation Example:
import React, { useEffect, useState } from "react";
import { fetchWithToken } from "studio/lib/fetchWithToken";
const MyCustomComponent: React.FC = () => {
const [data, setData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const query = '*[_type == "myType"]'; // Replace with your GROQ query
const result = await fetchWithToken<any>(query);
setData(result);
} catch (err) {
console.error("Error fetching data:", err);
setError(err.message);
}
};
fetchData();
}, []);
if (error) {
return <div>Error: {error}</div>;
}
return <div>{JSON.stringify(data)}</div>; // Render your component's UI
};
export default MyCustomComponent;
By using fetchWithToken, you ensure that all data fetching happens securely, with the server-side API route handling the sensitive token.
Some extra handling is required for translating paths with multiple slugs/segments, e.g. /kunder/fram
. In these cases, the translatePath
function in languageMiddleware.tsx
must be extended to handle the specific path structure. This usually involves checking which document type corresponds to the first path segment (e.g. kunder
corresponds to the customerCase
document type), and then finding the document for the second segment (e.g. customer case with slug fram
).
To enable preview functionality in the Presentation view, Sanity applies steganography to the string data. This manipulates the data to include invisible HTML entities to store various metadata. If the strings are used in business logic, that logic will likely break in the Presentation view. To fix this, Sanity provides the stegaClean
utility to remove this extra metadata. An example of this in action can be found in CompensationsPreview.tsx, where JSON parsing of salary data fails without stega cleaning.
As part of the Next.js middlewares setup, a matcher config is used to ignore certain paths. In most cases, static files stored in the /public
folder should not be passed through the middlewares. However, defining a regex that excludes all files from this folder can get messy and risks excluding too many paths. To make the regex matcher cleaner, it is encouraged that no files are placed directly in /public
. Instead, subfolders (e.g. /public/_assets
) should be defined and then excluded explicitly. Just make sure to not collide with other router paths (prefixing with _
reduces this risk).
As part of providing the basic metadata for the OpenGraph Protocol, a fallback image is generated if no other is specified. Fonts and background can be customized as shown below.
The following font utils file can be defined:
fonts must be placed in
/public/fonts
import { readFile } from "fs/promises";
import { join } from "path";
type Weight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
type FontStyle = "normal" | "italic";
interface FontOptions {
data: Buffer | ArrayBuffer;
name: string;
weight?: Weight;
style?: FontStyle;
lang?: string;
}
function readFont(filename: string) {
return readFile(join(process.cwd(), "public/fonts", filename));
}
export async function getFonts(): Promise<FontOptions[]> {
return [
{
data: await readFont("graphik_regular.woff"),
name: "Graphik",
weight: 400,
style: "normal",
},
{
data: await readFont("graphik_medium.woff"),
name: "Graphik",
weight: 600,
style: "normal",
},
{
data: await readFont("recoleta.ttf"),
name: "Recoleta",
weight: 600,
style: "normal",
},
];
}
followed by a modification of the image route response:
(<OpenGraphImage title={title} description={description ?? undefined} />),
{
width: 1200,
height: 630,
fonts: await getFonts(), // add this line
};
Simply use the CSS background property on the root element with a base64-encoded data url
background: url("data:image/png;base64,...");
TypeGen for Sanity was considered for this project in september 2024. At that time, it was not considered a good fit. This was mainly because of the tool being in beta, and concerns regarding ownership and flexibility of type definitions. See TYPEGEN.md for more details on the lessons learned.
Sanity Preview, or Presentation, is not yet fully supported in this project. Bugs and instabilities should be expected.
- Storybook Accessibility Testing: Storybook is set up with accessibility (a11y) testing tools, but you’ll need to add tests to each story manually.
-
Next.js: React framework for building server-rendered and statically-generated web applications.
-
Sanity: Headless CMS for structured content with a real-time API.
-
next-sanity: Next.js integration for Sanity with image optimization and preview support.
-
@types/react, @types/react-dom: TypeScript type definitions for React.
-
eslint: JavaScript linter.
-
typescript: Typed superset of JavaScript.
- Next.js Documentation - Learn more about the Next.js framework.
- Sanity Documentation - Explore Sanity’s powerful CMS features.
- Storybook Documentation - Get started with component-driven development.
- Sanity Slack Community - Join the community for support and collaboration.
This project is based on Sankitty, created by Christina Roise.