Erwin Smit Blog

website·Indexed 30 days ago

https://www.erwinsmit.com/

A technical blog by frontend and fullstack developer Erwin Smit, featuring guides and tutorials on Sitecore JSS, Next.js, Azure Functions, Azure Static Web Apps, and Apollo Federation.

Tokens
12.3K
Snippets
50
Records
67
Agent score
57%

What's inside Erwin Smit

  1. Choose a styling strategy compatible with React Server Components

    Avoid traditional CSS-in-JS (like Emotion or styled-components) if the project requires Server-Side Generation or React Server Components, as these technologies are strongly discouraged for server-side use.

    Alternative styling options include:

    • Vanilla Extract: Recommended for those wanting TypeScript support and CSS Modules behavior without the runtime overhead of CSS-in-JS. It provides compile-time validation for theme variables.
    • Tailwind CSS: Recommended for simplicity and rapid development within TSX files, though it lacks a compiler to validate class name spelling.
    • SCSS with BEM: A traditional approach, though it may suffer from architectural degradation over time and lacks the developer experience of type-safe styling.
  2. Host Sitecore JSS Next.js on Azure using YARP as a reverse proxy

    To host a Sitecore JSS Next.js application on Azure without using Vercel or legacy Azure Functions proxies, use a .NET application with YARP (Yet Another Reverse Proxy). YARP allows most proxy rules to be configured via appsettings.json without requiring extensive custom code. To serve static files from the Next.js build (such as the /_next path), the file share must be mounted in the .NET application using a PhysicalFileProvider.
    var nextMount = builder.Configuration.GetValue<string>("NextMount");
    
    app.UseStaticFiles(new StaticFileOptions {
        FileProvider = new PhysicalFileProvider(nextMount),
        RequestPath = "/_next"
    });
  3. Apply development rules for scalable Sitecore JSS components

    Follow these rules to maintain a clean separation between the design system and the CMS integration:

    1. Use Theme Variables: Avoid hard-coded spacing and colors. Use theme variables to allow components to be adjusted globally across different contexts.
    2. No Markup/Styling in the App Layer: Do not add class names or inline styles to React components within the Sitecore consuming Next.js apps. All styling must reside in the UI components package to ensure the Storybook design system remains the single source of truth.
    3. Use GraphQL Aliases: When writing integrated or connected GraphQL queries, use aliases. This ensures that if property names change in Sitecore, only the query needs updating, while the rendering component remains functional.
  4. Initialize OrderCloud SDK in a Next.js application

    Initialize the OrderCloud JavaScript SDK in _app.tsx to establish a connection and authenticate anonymously. This ensures OrderCloud functionality is available throughout the application.
    import { Configuration, Tokens, Auth } from "ordercloud-javascript-sdk";
    
    (async function(){
        const clientId = process.env.NEXT_PUBLIC_OC_CLIENT_ID || "";
        const baseApiUrl = process.env.NEXT_PUBLIC_OC_BASE_API_URL;
        const scope = process.env.NEXT_PUBLIC_OC_SCOPE?.split(",");
    
        Configuration.Set({
          clientID: clientId,
          baseApiUrl: baseApiUrl,
          cookieOptions: { prefix: "hds-nextjs", path: "/" },
        });
    
        const authResponse = await Auth.Anonymous(clientId, scope || ([""] as ApiRole[]));
    
        Tokens.SetAccessToken(authResponse.access_token);
    
    })();
  5. Add Scriban support to VS Code

    VS Code does not support Scriban by default. To enable syntax highlighting and autocompletion, install a Scriban extension. For Sitecore developers, the 'Sitecore Scriban Extension' is recommended as it provides autocompletion for Sitecore-specific methods and objects.
  6. Pass authentication from Apollo Gateway to subgraphs

    Since the Apollo Gateway handles the initial request, authentication tokens (e.g., Firebase ID tokens) should be verified at the gateway level. The resulting user identity (like userId) is placed in the gateway's context and then forwarded to underlying services via HTTP headers using a custom RemoteGraphQLDataSource implementation.
    // 1. Extract userId in Gateway context
    const server = new ApolloServer({
      gateway,
      context: async ({ req }) => {
        const token = req.headers.authorization || "";
        try {
          const user = await firebaseApp.auth().verifyIdToken(token);
          return { userId: user.user_id };
        } catch (e) {
          return { userId: undefined };
        }
      },
    });
    
    // 2. Forward userId to subgraphs via headers
    class AuthenticatedDataSource extends RemoteGraphQLDataSource {
      willSendRequest({ request, context }) {
        if (context.userId) {
          request.http.headers.set("userid", context.userId);
        }
      }
    }
  7. Eliminate render-blocking hydration scripts in Sitecore React

    In many Sitecore React implementations, inline script tags used for hydration block the rendering of subsequent HTML. To fix this, wrap the hydration scripts in HTML comments and add a custom attribute (e.g., data-react-hydrate) to identify them. Then, use a client-side script to find these commented-out blocks, extract the JavaScript, and execute them dynamically using the Function constructor. This allows the main client bundle to be moved to the bottom of the page without breaking the hydration process.

    <!-- Example of commented out hydration script --> <!--<script data-react-hydrate> ReactDOM.hydrate(React.createElement(ClientX.default.Navigation, { ... })) </script>-->

    <!-- Client-side execution logic --> <script> import "expose-loader?exposes=React!react"; import "expose-loader?exposes=ReactDOM!react-dom"; import "expose-loader?exposes=ClientX!../src/components";

    const bodyHtml = document.body.innerHTML; const scriptTagExpression = /(?=<!--<script data-react-hydrate)([\s\S]*?)-->/gm; const scriptTagContentsExpression = /^<!--|-->$|<script data-react-hydrate>|</script>/g;

    const matches = scriptTagExpression.exec(bodyHtml); matches?.forEach((match) => { const scriptTag = match.replace(scriptTagContentsExpression, "");

    try { Function(scriptTag)(); } catch (e) { console.error("hydrating error:", e, scriptTag); } return ""; }); </script>

  8. Configure GitHub Actions workflow for AzCopy Azure Function deployment

    This workflow uses the kheiakiyama/install-azcopy-action to install the AzCopy tool, cleans the remote wwwroot directory, and copies the build artifacts to the Azure storage account using a SAS token for authentication.
    name: Deploy Azure Function
    
    on: [workflow_dispatch]
    
    env:
      NODE_VERSION: "14.x"
      # Format: https://{storageaccount}.file.core.windows.net/{filesharename}/site/wwwroot
      AZUREFUNCDEST: "https://${pathtostoreageaccount}.file.core.windows.net/${filesharename}/site/wwwroot"
    
    jobs:
      build-and-deploy:
        runs-on: windows-latest
        steps:
          - name: "Checkout GitHub Action"
            uses: actions/checkout@main
    
          - name: Setup Node ${{ env.NODE_VERSION }} Environment
            uses: actions/setup-node@v1
            with:
              node-version: ${{ env.NODE_VERSION }}
    
          - name: Install azcopy
            uses: kheiakiyama/install-azcopy-action@v1.0.3
    
          - name: "Build application"
            shell: pwsh
            run: |
              npm run build
    
          - name: Clean remote directory
            uses: kheiakiyama/install-azcopy-action@v1
            with:
              version: "v10"
            # Note: SECRETSASTOKEN is a GitHub Secret containing the SAS token
          - run: azcopy_v10 rm '${{ env.AZUREFUNCDEST }}?${{ secrets.SECRETSASTOKEN }}' --recursive
    
          - name: Copy files to Azure
            uses: kheiakiyama/install-azcopy-action@v1
            with:
              version: "v10"
          - run: azcopy_v10 copy './packages/azure-functions/*' '${{ env.AZUREFUNCDEST }}?${{ secrets.SECRETSASTOKEN }}' --recursive
  9. Implement Azure Blob Storage for Sitecore JSS Editing Data Cache on SWA

    Azure Static Web Apps (SWA) does not support read/write access to the /tmp directory, causing EditingDataDiskCache to fail with 500 errors in the Experience Editor. To fix this, replace the default disk cache with a custom implementation using Azure Blob Storage via the @azure/storage-blob package. This allows preview data to be persisted in a Storage Account container.
    import { BlobServiceClient, ContainerClient } from '@azure/storage-blob';
    
    export class EditingDataBlobCache implements EditingDataCache {
      private containerClient: ContainerClient;
    
      constructor() {
        const blobServiceClient = BlobServiceClient.fromConnectionString(
          process.env.AZURE_STORAGE_CONNECTION_STRING
        );
    
        this.containerClient = blobServiceClient.getContainerClient(process.env.AZURE_BLOB_CONTAINER);
      }
    
      async set(key: string, editingData: EditingData): Promise<void> {
        const blobName = key + '.txt';
        const blockBlobClient = this.containerClient.getBlockBlobClient(blobName);
        const data = JSON.stringify(editingData);
        await blockBlobClient.upload(data, data.length);
      }
      // implement get method similarly
    }
  10. Apply a temporary JS fix to Azure DevOps using Fiddler AutoResponder

    To resolve the childFolders runtime error in Azure DevOps without waiting for an official patch, you can use Fiddler to inject a modified version of the frontend JavaScript bundle:

    1. Download the JavaScript bundle responsible for the error from the cdn.vsassets.io URL found in the network trace.
    2. Beautify the code and locate the createFoldersStructure function.
    3. Replace the failing line o[p.toLowerCase()].childFolders.push(c) with a ternary check: o[p.toLowerCase()] ? o[p.toLowerCase()].childFolders.push(c) : null.
    4. Configure Fiddler's AutoResponder to intercept the request for the original JS bundle and serve the modified local file instead.
    5. Ensure Fiddler is configured to decrypt HTTPS traffic by enabling the 'Decrypt HTTPS traffic' checkbox in settings to allow the certificate injection necessary for intercepting CDN calls.
  11. Serve Next.js static assets via Azure Functions

    Next.js static assets (e.g., _next/static/...) are not automatically served by the custom server in an Azure Function environment. This is solved by creating a specific proxy to intercept static file requests and a corresponding Azure Function (serveStaticFile) to read the files from the .next/static directory using the filesystem (fs) module.
    // proxies.json configuration
    {
      "proxies": {
        "staticFiles": {
          "disabled": false,
          "matchCondition": {
            "methods": [ "GET", "OPTIONS" ],
            "route": "_next/static/{*path}"
          },
          "backendUri": "https://localhost/serveStaticFile?path={path}"
        }
      }
    }
    // serveStaticFile function
    import { Context, HttpRequest } from "@azure/functions";
    const fs = require('fs').promises;
    const path = require("path");
    
    const nextPath = path.dirname(require.resolve("../../basic-nextjs-example/.next/BUILD_ID"));
    
    module.exports = async function (context: Context, req: HttpRequest) {
        const staticFilePath = req?.query?.path;
        try {
            const data = await fs.readFile(path.join(nextPath, "static", staticFilePath));
            return { body: data };
        } catch {
            return { body: `File not found ${JSON.stringify(req.query)}` };
        }
    }