
Como criar um conversor de HTML para PDF em .NET
A automatização da entrega de documentos é um requisito que surge em praticamente todas as aplicações .NET de linha de negócios. Assim que um pedido é feito, a fatura deve chegar ao cliente em questão de segundos. Quando um relatório é gerado durante a noite, as partes interessadas esperam recebê-lo em suas caixas de entrada antes de chegarem ao escritório. O formato de entrega mais simples e universalmente aceito é um PDF enviado como anexo de e-mail. Este guia orienta você através do fluxo de trabalho completo de ponta a ponta em C# -- gerando um documento PDF na memória com IronPDF e depois enviando-o como um anexo de email usando MailKit ou o namespace embutido System.Net.Mail, tudo sem escrever um único byte no disco.
Como instalar os pacotes necessários?
Este fluxo de trabalho é alimentado por dois pacotes: uma biblioteca para geração de PDFs e uma biblioteca para envio de e-mails. Instale ambos através do Console do Gerenciador de Pacotes no Visual Studio ou através da CLI do .NET .
O IronPDF traz um mecanismo de renderização baseado no Chromium que converte HTML, CSS e JavaScript em documentos PDF com qualidade de pixel perfeita. Ele funciona no Windows, Linux e macOS, o que significa que o mesmo código funciona em uma API Web ASP.NET Core , um serviço em segundo plano ou uma Função do Azure. MailKit é a biblioteca que a Microsoft recomenda para todo novo desenvolvimento de e-mail em .NET -- ela oferece suporte a SMTP, IMAP, POP3, OAuth 2.0 e construção completa de MIME. O código-fonte e a documentação do MailKit estão disponíveis no GitHub.
Como gerar um documento PDF na memória?
A classe ChromePdfRenderer é o ponto de entrada para a conversão de HTML para PDF. Passe uma string HTML para RenderHtmlAsPdf e você receberá de volta um objeto PdfDocument que reside inteiramente na memória. Acesse os bytes brutos através da propriedade BinaryData -- este array de bytes é exatamente o que as APIs de anexo de email esperam.
using IronPdf;
var renderer = new ChromePdfRenderer();
string htmlContent = """
<h1>Order Confirmation</h1>
<p>Thank you for your purchase.</p>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>Widget A</td><td>2</td><td>$19.99</td></tr>
<tr><td>Widget B</td><td>1</td><td>$59.99</td></tr>
</table>
<p><strong>Order Total: $99.97</strong></p>
""";
PdfDocument pdf = renderer.RenderHtmlAsPdf(htmlContent);
// pdf.BinaryData holds the complete PDF as a byte array
byte[] pdfBytes = pdf.BinaryData;
Console.WriteLine($"PDF generated: {pdfBytes.Length} bytes");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim htmlContent As String = "
<h1>Order Confirmation</h1>
<p>Thank you for your purchase.</p>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>Widget A</td><td>2</td><td>$19.99</td></tr>
<tr><td>Widget B</td><td>1</td><td>$59.99</td></tr>
</table>
<p><strong>Order Total: $99.97</strong></p>
"
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(htmlContent)
' pdf.BinaryData holds the complete PDF as a byte array
Dim pdfBytes As Byte() = pdf.BinaryData
Console.WriteLine($"PDF generated: {pdfBytes.Length} bytes")O método RenderHtmlAsPdf analisa o HTML usando o mesmo mecanismo Chromium que alimenta o Google Chrome, de modo que tabelas, CSS Grid, Flexbox e fontes incorporadas são renderizadas exatamente como em um navegador. O resultado é um PdfDocument cuja propriedade BinaryData retorna o binário completo do PDF sem quaisquer leituras e gravações em disco. Para documentos que incorporam imagens ou folhas de estilo externas, use o parâmetro opcional BasePath para informar o IronPDF onde resolver URLs de recursos relativos -- isso é coberto em detalhe na página de como fazer de HTML para PDF.
Configurar o layout da página e cabeçalhos personalizados
Antes de anexar o PDF, você pode configurar as margens, cabeçalhos ou rodapés. Todas as opções de layout residem na propriedade RenderingOptions:
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 15;
renderer.RenderingOptions.MarginBottom = 15;
renderer.RenderingOptions.MarginLeft = 12;
renderer.RenderingOptions.MarginRight = 12;
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
HtmlFragment = "<div style='font-size:9pt;color:#666;text-align:right;'>Monthly Report</div>",
DrawDividerLine = true
};
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = "<div style='font-size:8pt;text-align:center;'>{page} of {total-pages}</div>"
};
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Summary</h1><p>See attached data.</p>");
byte[] pdfBytes = pdf.BinaryData;Imports IronPdf
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.MarginTop = 15
renderer.RenderingOptions.MarginBottom = 15
renderer.RenderingOptions.MarginLeft = 12
renderer.RenderingOptions.MarginRight = 12
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
.HtmlFragment = "<div style='font-size:9pt;color:#666;text-align:right;'>Monthly Report</div>",
.DrawDividerLine = True
}
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
.HtmlFragment = "<div style='font-size:8pt;text-align:center;'>{page} of {total-pages}</div>"
}
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf("<h1>Monthly Summary</h1><p>See attached data.</p>")
Dim pdfBytes As Byte() = pdf.BinaryDataAs margens estão em milímetros. Os tokens {page} e {total-pages} são substituídos no momento da renderização. A página de como fazer de string HTML para PDF cobre o conjunto completo de opções de renderização. Você também pode adicionar marcas d'água ou carimbos de texto e imagens ao documento gerado antes de anexá-lo.
Como anexar um PDF a um e-mail com o MailKit?
O MailKit constrói uma árvore de mensagens MIME diretamente, dando a você controle total sobre o tipo de conteúdo, a codificação e os metadados dos anexos. A classe auxiliar BodyBuilder simplifica o caso comum de um corpo de texto ou HTML com um ou mais anexos de arquivos.
using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
// Step 1 -- generate the PDF in memory
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Monthly Report</h1><p>Generated automatically.</p>");
// Step 2 -- build the email message
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Reports Service", "reports@example.com"));
message.To.Add(new MailboxAddress("Alice Smith", "alice@example.com"));
message.Subject = "Your Monthly Report is Ready";
var builder = new BodyBuilder();
builder.TextBody = "Hello Alice,\n\nPlease find your monthly report attached.\n\nRegards,\nReports Service";
builder.HtmlBody = "<p>Hello Alice,</p><p>Please find your monthly report attached.</p>";
// Add the in-memory PDF as an attachment
builder.Attachments.Add("MonthlyReport.pdf", pdf.BinaryData, new ContentType("application", "pdf"));
message.Body = builder.ToMessageBody();
// Step 3 -- send via SMTP with TLS
using var client = new SmtpClient();
await client.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("username", "app-password");
await client.SendAsync(message);
await client.DisconnectAsync(true);Imports IronPdf
Imports MailKit.Net.Smtp
Imports MailKit.Security
Imports MimeKit
' Step 1 -- generate the PDF in memory
Dim renderer As New ChromePdfRenderer()
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf("<h1>Monthly Report</h1><p>Generated automatically.</p>")
' Step 2 -- build the email message
Dim message As New MimeMessage()
message.From.Add(New MailboxAddress("Reports Service", "reports@example.com"))
message.To.Add(New MailboxAddress("Alice Smith", "alice@example.com"))
message.Subject = "Your Monthly Report is Ready"
Dim builder As New BodyBuilder()
builder.TextBody = "Hello Alice," & vbCrLf & vbCrLf & "Please find your monthly report attached." & vbCrLf & vbCrLf & "Regards," & vbCrLf & "Reports Service"
builder.HtmlBody = "<p>Hello Alice,</p><p>Please find your monthly report attached.</p>"
' Add the in-memory PDF as an attachment
builder.Attachments.Add("MonthlyReport.pdf", pdf.BinaryData, New ContentType("application", "pdf"))
message.Body = builder.ToMessageBody()
' Step 3 -- send via SMTP with TLS
Using client As New SmtpClient()
Await client.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls)
Await client.AuthenticateAsync("username", "app-password")
Await client.SendAsync(message)
Await client.DisconnectAsync(True)
End Usingbuilder.Attachments.Add aceita três argumentos: o nome do arquivo que o destinatário vê em seu cliente de email, o array de bytes brutos de pdf.BinaryData, e uma instância ContentType especificando o tipo MIME como application/pdf. Os métodos SMTP assíncronos mantêm a thread de chamada livre enquanto a operação de rede é concluída — algo crucial em controladores ASP.NET Core que lidam com dezenas de solicitações simultâneas.
SecureSocketOptions.StartTls negocia um canal criptografado com o servidor SMTP na porta 587. Para o Gmail, use uma Senha de Aplicativo em vez da senha da sua conta: gere uma em Segurança da Conta Google > Senhas de Aplicativos, depois passe-a para AuthenticateAsync. Para o Microsoft 365, configure a autenticação OAuth 2.0 por meio da classe SaslMechanismOAuth2 do MailKit se seu inquilino desativou a autenticação básica.
Envio para vários destinatários
Para copiar várias pessoas no mesmo email, adicione endereços às coleções To, Cc, ou Bcc antes de chamar SendAsync:
message.To.Add(new MailboxAddress("Alice Smith", "alice@example.com"));
message.To.Add(new MailboxAddress("Bob Jones", "bob@example.com"));
message.Cc.Add(new MailboxAddress("Carol Manager", "carol@example.com"));message.To.Add(New MailboxAddress("Alice Smith", "alice@example.com"))
message.To.Add(New MailboxAddress("Bob Jones", "bob@example.com"))
message.Cc.Add(New MailboxAddress("Carol Manager", "carol@example.com"))Uma única chamada SendAsync entrega a mensagem para todos os endereços. O MailKit agrupa os comandos RCPT TO em uma única sessão SMTP, portanto não há perda de desempenho para vários destinatários.
Como usar o .NET como alternativa?
Para projetos direcionados a versões .NET mais antigas ou projetos onde adicionar um pacote NuGet de terceiros não é permitido, o namespace embutido System.Net.Mail lida com a entrega básica de SMTP. A Microsoft já não o recomenda para novos desenvolvimentos, mas abrange os casos de uso comuns sem dependências adicionais.
using IronPdf;
using System.Net;
using System.Net.Mail;
// Generate the PDF
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Invoice #1001</h1><p>Amount due: $350.00</p>");
// Build the mail message
using var message = new MailMessage("invoices@example.com", "customer@example.com");
message.Subject = "Invoice #1001 Attached";
message.Body = "Your invoice is attached to this email. Please remit payment within 30 days.";
message.IsBodyHtml = false;
// Wrap the byte array in a MemoryStream for the Attachment constructor
var stream = new MemoryStream(pdf.BinaryData);
message.Attachments.Add(new Attachment(stream, "Invoice-1001.pdf", "application/pdf"));
// Send via SMTP
using var client = new SmtpClient("smtp.example.com", 587)
{
Credentials = new NetworkCredential("username", "password"),
EnableSsl = true
};
await client.SendMailAsync(message);Imports IronPdf
Imports System.Net
Imports System.Net.Mail
Imports System.IO
' Generate the PDF
Dim renderer As New ChromePdfRenderer()
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf("<h1>Invoice #1001</h1><p>Amount due: $350.00</p>")
' Build the mail message
Using message As New MailMessage("invoices@example.com", "customer@example.com")
message.Subject = "Invoice #1001 Attached"
message.Body = "Your invoice is attached to this email. Please remit payment within 30 days."
message.IsBodyHtml = False
' Wrap the byte array in a MemoryStream for the Attachment constructor
Dim stream As New MemoryStream(pdf.BinaryData)
message.Attachments.Add(New Attachment(stream, "Invoice-1001.pdf", "application/pdf"))
' Send via SMTP
Using client As New SmtpClient("smtp.example.com", 587)
client.Credentials = New NetworkCredential("username", "password")
client.EnableSsl = True
Await client.SendMailAsync(message)
End Using
End UsingA principal diferença em comparação com o MailKit é que System.Net.Mail.Attachment não aceita diretamente um array de bytes -- você deve envolver pdf.BinaryData primeiro em um MemoryStream. Tanto MailMessage quanto SmtpClient estão envolvidos em declarações using, que descartam a conexão SMTP e liberam o fluxo subjacente após o envio. Se você omitir o using em MailMessage, o fluxo de anexos pode ser descartado antes que o envio seja concluído em alguns tempos de execução.
Escolhendo entre MailKit e .NET
| Recurso | MailKit | System.Net.Mail |
|---|---|---|
| Autenticação OAuth 2.0 | Sim | Não |
| Suporte a IMAP/POP3 | Sim | Não |
| API assíncrona em primeiro lugar | Sim | Parcial |
| Recomendação da Microsoft | Recomendado | Legado |
| Pacote NuGet adicional | Obrigatório | Não é necessário |
| Construção MIME complexa | Apoio total | Básico |
Escolha o MailKit para qualquer novo projeto, especialmente se o servidor SMTP exigir OAuth ou se você precisar de IMAP para ler as respostas. Use System.Net.Mail quando a base de código já depende dele e o custo da migração não é justificado.
Como você aplica esse padrão a fluxos de trabalho empresariais reais?
O padrão de conversão de PDF para e-mail em memória aplica-se diretamente aos cenários de automação de documentos que impulsionam a maioria das aplicações empresariais.
Automação de Faturas
Um sistema de gerenciamento de pedidos de comércio eletrônico gera uma fatura em PDF imediatamente após a captura do pagamento. O evento OrderConfirmed aciona um método que chama RenderHtmlAsPdf com uma string HTML templateada por Razor populada com dados de pedido, depois envia o resultado para o endereço de email do cliente. Como o PDF nunca toca o sistema de arquivos, não há arquivos restantes para limpar, nenhuma condição de corrida em um diretório temporário compartilhado e nenhum problema de permissão em implantações em contêiner. Para obter mais informações sobre como renderizar HTML a partir de views Razor , consulte o guia de geração de PDF do ASP.NET Core .
Distribuição programada de relatórios
Um serviço em segundo plano agendado com IHostedService gera um resumo analítico semanal às 06:00 toda segunda-feira. O processo consulta o banco de dados, gera uma string de relatório HTML, renderiza-a com o IronPDF e usa o MailKit para enviá-la a uma lista de distribuição. Todo o pipeline é executado como um fluxo de trabalho assíncrono, portanto, não mantém uma thread alocada no pool de threads durante o handshake SMTP. Para cargas de trabalho hospedadas no Azure, o guia do gerador de PDF do Azure explica como implantar o IronPDF no Azure App Service e no Azure Functions.
Geração de recibos no ASP.NET Core
Em uma API mínima ou ação de controlador do ASP.NET Core , um endpoint POST recebe uma carga útil de finalização de compra, gera um PDF do recibo e retorna um HTTP 200 enquanto simultaneamente envia o e-mail. Mantenha a lógica de envio de email em um Task em segundo plano para que a resposta HTTP retorne ao cliente imediatamente:
app.MapPost("/checkout", async (CheckoutRequest req, IEmailService emailService) =>
{
var renderer = new ChromePdfRenderer();
PdfDocument receipt = renderer.RenderHtmlAsPdf(BuildReceiptHtml(req));
// Fire and forget -- do not await so the HTTP response is immediate
_ = emailService.SendReceiptAsync(req.CustomerEmail, receipt.BinaryData);
return Results.Ok(new { message = "Order confirmed." });
});Imports System.Threading.Tasks
app.MapPost("/checkout", Async Function(req As CheckoutRequest, emailService As IEmailService) As Task(Of IResult)
Dim renderer As New ChromePdfRenderer()
Dim receipt As PdfDocument = renderer.RenderHtmlAsPdf(BuildReceiptHtml(req))
' Fire and forget -- do not await so the HTTP response is immediate
_ = emailService.SendReceiptAsync(req.CustomerEmail, receipt.BinaryData)
Return Results.Ok(New With {.message = "Order confirmed."})
End Function)Isso mantém o tempo de resposta da API abaixo de 100 ms, mesmo quando o servidor SMTP está lento. O emailService é registrado como um serviço escopo ou transitório que envolve o SmtpClient do MailKit.
Como você lida com erros e novas tentativas?
As operações de rede falham. Os servidores SMTP estão temporariamente indisponíveis, os tokens de autenticação expiram e os limites de tamanho dos anexos variam conforme o provedor. Incorpore resiliência ao processo de envio de e-mails desde o início.
Envolva a lógica de envio do MailKit em um bloco try/catch e registre as falhas em uma fila persistente para que possam ser repetidas:
using IronPdf;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using Microsoft.Extensions.Logging;
async Task SendPdfEmailWithRetryAsync(
byte[] pdfBytes,
string recipientEmail,
string subject,
ILogger logger,
int maxAttempts = 3)
{
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Mailer", "mailer@example.com"));
message.To.Add(MailboxAddress.Parse(recipientEmail));
message.Subject = subject;
var builder = new BodyBuilder { TextBody = "Your document is attached." };
builder.Attachments.Add("document.pdf", pdfBytes, new ContentType("application", "pdf"));
message.Body = builder.ToMessageBody();
using var smtpClient = new SmtpClient();
await smtpClient.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls);
await smtpClient.AuthenticateAsync("user", "pass");
await smtpClient.SendAsync(message);
await smtpClient.DisconnectAsync(true);
logger.LogInformation("Email sent to {Email} on attempt {Attempt}", recipientEmail, attempt);
return;
}
catch (Exception ex) when (attempt < maxAttempts)
{
logger.LogWarning(ex, "Send attempt {Attempt} failed. Retrying...", attempt);
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
}Imports IronPdf
Imports MailKit.Net.Smtp
Imports MailKit.Security
Imports MimeKit
Imports Microsoft.Extensions.Logging
Public Async Function SendPdfEmailWithRetryAsync(
pdfBytes As Byte(),
recipientEmail As String,
subject As String,
logger As ILogger,
Optional maxAttempts As Integer = 3) As Task
For attempt As Integer = 1 To maxAttempts
Try
Dim message = New MimeMessage()
message.From.Add(New MailboxAddress("Mailer", "mailer@example.com"))
message.To.Add(MailboxAddress.Parse(recipientEmail))
message.Subject = subject
Dim builder = New BodyBuilder With {
.TextBody = "Your document is attached."
}
builder.Attachments.Add("document.pdf", pdfBytes, New ContentType("application", "pdf"))
message.Body = builder.ToMessageBody()
Using smtpClient = New SmtpClient()
Await smtpClient.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls)
Await smtpClient.AuthenticateAsync("user", "pass")
Await smtpClient.SendAsync(message)
Await smtpClient.DisconnectAsync(True)
End Using
logger.LogInformation("Email sent to {Email} on attempt {Attempt}", recipientEmail, attempt)
Return
Catch ex As Exception When attempt < maxAttempts
logger.LogWarning(ex, "Send attempt {Attempt} failed. Retrying...", attempt)
Await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)))
End Try
Next
End FunctionO recuo exponencial — 2 segundos após a primeira falha, 4 após a segunda — evita sobrecarregar um servidor SMTP já saturado. Em aplicações de produção, substitua o loop de repetição por uma fila de mensagens (Azure Service Bus, RabbitMQ ou AWS SQS) para que as falhas persistam mesmo após reinicializações da aplicação.
O IronPDF também lança um PdfException se o conteúdo HTML não puder ser renderizado. Capture-o separadamente das exceções SMTP para que a mensagem de erro seja específica:
PdfDocument pdf;
try
{
pdf = renderer.RenderHtmlAsPdf(htmlContent);
}
catch (IronPdf.Exceptions.PdfException ex)
{
logger.LogError(ex, "PDF rendering failed");
throw;
}Imports IronPdf.Exceptions
Dim pdf As PdfDocument
Try
pdf = renderer.RenderHtmlAsPdf(htmlContent)
Catch ex As PdfException
logger.LogError(ex, "PDF rendering failed")
Throw
End TrySeparar os erros de renderização dos erros de entrega torna a depuração mais rápida. Para uma visão mais abrangente sobre o tratamento de erros em fluxos de trabalho automatizados de documentos, o guia de geração de PDF em 5 etapas aborda os padrões de validação em detalhes.
Como manter o tamanho do anexo dentro dos limites do provedor?
A maioria dos provedores de e-mail comerciais impõe um tamanho máximo para anexos. O Gmail limita anexos individuais a 25 MB; O Microsoft 365 define, por padrão, 20 MB para caixas de correio padrão. Um relatório HTML com formatação complexa e imagens incorporadas pode exceder esses limites inesperadamente.
Três técnicas ajudam a manter-se dentro dos limites:
Comprima as imagens antes de renderizá-las. Imagens embutidas devem usar JPEG ou WebP comprimidos em vez de PNG não comprimido. Um logotipo PNG de 600 dpi pode adicionar vários megabytes ao PDF; Uma imagem JPEG com 85% de qualidade normalmente tem menos de 200 KB para o mesmo resultado visual.
Use as configurações de compressão do IronPDF. O método PdfDocument.CompressImages reduz a resolução dos bitmaps incorporados após a renderização. Chame-o antes de ler BinaryData:
pdf.CompressImages(60); // quality 0-100
byte[] compressedPdfBytes = pdf.BinaryData;pdf.CompressImages(60) ' quality 0-100
Dim compressedPdfBytes As Byte() = pdf.BinaryDataDivida relatórios grandes em vários e-mails. Se um relatório exceder o limite do provedor mesmo após a compressão, gere um PDF por seção e envie cada um em um e-mail separado. A página de como fazer para dividir e mesclar PDFs mostra como dividir um PdfDocument por intervalo de páginas usando CopyPages.
Referências externas para limites de tamanho de SMTP: limites de anexos do Gmail , limites de tamanho de mensagens do Microsoft 365 .
Quais são os seus próximos passos?
Agora você tem um template funcional para gerar um PDF na memória com o IronPDF e enviá-lo como um anexo de email usando MailKit ou System.Net.Mail. A abordagem em memória elimina leituras e gravações de disco, simplifica implantações em contêineres e escala para cenários de alta taxa de transferência sem exigir limpeza de arquivos temporários.
Para aprofundar a integração:
Leia o tutorial de HTML para PDF para dominar layouts de documentos complexos com cabeçalhos, rodapés e numeração de páginas.
- Explore as opções de renderização de PDF personalizadas para controlar margens, tamanho da página e tempo limite de execução do JavaScript .
- Adicione uma assinatura digital ao PDF gerado antes de anexá-lo, fornecendo aos destinatários documentos invioláveis.
- Implante no Azure Functions ou no Serviço de Aplicativos para geração de documentos sem servidor em escala. Experimente o IronPDF gratuitamente para testar todos os recursos antes de adquirir uma licença de produção ou consulte as opções de licenciamento para obter informações sobre preços de implementação.

Curtis Chau é bacharel em Ciência da Computação (Universidade Carleton) e se especializa em desenvolvimento front-end, com experiência em Node.js, TypeScript, JavaScript e React. Apaixonado por criar interfaces de usuário intuitivas e esteticamente agradáveis, Curtis gosta de trabalhar com frameworks modernos e criar manuais bem estruturados e visualmente atraentes.
Artigos relacionados


